Builds APIs with Express including routing, middleware, error handling, and security. Use when creating Node.js APIs, building REST services, or adding middleware-based server functionality.
Builds Express.js APIs with routing, middleware, and error handling. Use when creating Node.js REST services, adding authentication, or implementing server-side validation.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Fast, unopinionated, minimalist web framework for Node.js.
Install:
npm install express
npm install -D @types/express typescript
Create server:
// src/index.ts
import express from 'express';
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: 'Hello World!' });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
src/
index.ts # App entry
routes/ # Route handlers
users.ts
posts.ts
middleware/ # Custom middleware
auth.ts
validation.ts
controllers/ # Business logic
userController.ts
services/ # Data access
userService.ts
types/ # TypeScript types
index.ts
import express from 'express';
const app = express();
// HTTP methods
app.get('/users', (req, res) => res.json({ users: [] }));
app.post('/users', (req, res) => res.status(201).json(req.body));
app.put('/users/:id', (req, res) => res.json({ id: req.params.id }));
app.patch('/users/:id', (req, res) => res.json({ patched: true }));
app.delete('/users/:id', (req, res) => res.status(204).send());
// All methods
app.all('/api/*', (req, res, next) => {
console.log('API request');
next();
});
// Path parameters
app.get('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ userId: id });
});
// Multiple params
app.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({ postId, commentId });
});
// Optional params
app.get('/users/:id?', (req, res) => {
if (req.params.id) {
res.json({ userId: req.params.id });
} else {
res.json({ message: 'All users' });
}
});
// Regex params
app.get('/user/:id(\\d+)', (req, res) => {
// Only matches numeric IDs
res.json({ id: req.params.id });
});
app.get('/search', (req, res) => {
const { q, page = '1', limit = '10' } = req.query;
res.json({
query: q,
page: Number(page),
limit: Number(limit),
});
});
// routes/users.ts
import { Router } from 'express';
const router = Router();
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.get('/:id', (req, res) => {
res.json({ id: req.params.id });
});
router.post('/', (req, res) => {
res.status(201).json(req.body);
});
export default router;
// index.ts
import userRoutes from './routes/users';
app.use('/api/users', userRoutes);
import express from 'express';
const app = express();
// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import compression from 'compression';
app.use(cors()); // CORS
app.use(helmet()); // Security headers
app.use(morgan('dev')); // Logging
app.use(compression()); // Gzip compression
import { Request, Response, NextFunction } from 'express';
// Logging middleware
const logger = (req: Request, res: Response, next: NextFunction) => {
console.log(`${req.method} ${req.path}`);
next();
};
// Timing middleware
const timing = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.path} - ${Date.now() - start}ms`);
});
next();
};
app.use(logger);
app.use(timing);
const requireAuth = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch {
return res.status(403).json({ error: 'Invalid token' });
}
};
// Apply to specific routes
app.get('/profile', requireAuth, (req, res) => {
res.json({ user: req.user });
});
// Apply to router
router.use(requireAuth);
app.post('/api/data', (req, res) => {
// Body (requires express.json())
const { name, email } = req.body;
// Query params
const { page } = req.query;
// Route params
const { id } = req.params;
// Headers
const authHeader = req.get('Authorization');
const contentType = req.get('Content-Type');
// Cookies (requires cookie-parser)
const sessionId = req.cookies.sessionId;
// IP address
const ip = req.ip;
// HTTP method
const method = req.method;
// Original URL
const url = req.originalUrl;
res.json({ received: true });
});
app.get('/api/examples', (req, res) => {
// JSON response
res.json({ message: 'Hello' });
// With status code
res.status(201).json({ created: true });
// Text response
res.send('Hello World');
// HTML response
res.send('<h1>Hello</h1>');
// Redirect
res.redirect('/new-path');
res.redirect(301, '/permanent-redirect');
// Download file
res.download('/path/to/file.pdf');
// Send file
res.sendFile('/path/to/file.html');
// Set headers
res.set('X-Custom-Header', 'value');
res.set({
'Content-Type': 'application/json',
'X-Custom': 'value',
});
// Set cookie
res.cookie('name', 'value', { httpOnly: true, secure: true });
// Clear cookie
res.clearCookie('name');
// Status only
res.sendStatus(204); // No Content
});
import { Request, Response, NextFunction } from 'express';
interface AppError extends Error {
statusCode?: number;
status?: string;
}
// Custom error class
class HttpError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
}
}
// Error handler middleware (must have 4 params)
const errorHandler = (
err: AppError,
req: Request,
res: Response,
next: NextFunction
) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
error: {
message,
status: statusCode,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
});
};
// Register after all routes
app.use(errorHandler);
// Wrapper for async handlers
const asyncHandler = (fn: Function) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await findUser(req.params.id);
if (!user) {
throw new HttpError('User not found', 404);
}
res.json(user);
}));
// Place after all routes
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
import { z } from 'zod';
const createUserSchema = z.object({
body: z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).optional(),
}),
});
const validate = (schema: z.ZodSchema) => {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse({ body: req.body, query: req.query, params: req.params });
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
next(error);
}
};
};
app.post('/users', validate(createUserSchema), (req, res) => {
// req.body is validated
res.status(201).json(req.body);
});
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET!;
// Login
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });
res.json({ token });
});
// Auth middleware
const authenticate = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
req.userId = decoded.userId;
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
};
// Protected route
app.get('/profile', authenticate, (req, res) => {
res.json({ userId: req.userId });
});
import multer from 'multer';
import path from 'path';
const storage = multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, uniqueSuffix + path.extname(file.originalname));
},
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/gif'];
cb(null, allowed.includes(file.mimetype));
},
});
// Single file
app.post('/upload', upload.single('file'), (req, res) => {
res.json({ file: req.file });
});
// Multiple files
app.post('/uploads', upload.array('files', 5), (req, res) => {
res.json({ files: req.files });
});
import request from 'supertest';
import { describe, it, expect } from 'vitest';
import app from './app';
describe('Users API', () => {
it('GET /users returns users', async () => {
const response = await request(app)
.get('/api/users')
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('users');
});
it('POST /users creates user', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' })
.expect(201);
expect(response.body.name).toBe('John');
});
});
| Mistake | Fix |
|---|---|
| Forgetting express.json() | Add app.use(express.json()) |
| Not handling async errors | Use asyncHandler wrapper |
| Error handler with 3 params | Must have 4 params for Express |
| Sending response twice | Return after sending |
| Not using CORS | Add cors() middleware |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.