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.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: express description: 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.
Express
Fast, unopinionated, minimalist web framework for Node.js.
Quick Start
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}`);
});
Project Structure
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
Routing
Basic Routes
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();
});
Route Parameters
// 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 });
});
Query Parameters
app.get('/search', (req, res) => {
const { q, page = '1', limit = '10' } = req.query;
res.json({
query: q,
page: Number(page),
limit: Number(limit),
});
});
Router Modules
// 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);
Middleware
Built-in Middleware
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'));
Third-party Middleware
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
Custom Middleware
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);
Route-specific Middleware
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);
Request & Response
Request Object
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 });
});
Response Methods
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
});
Error Handling
Error Handler Middleware
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);
Async Error Handling
// 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);
}));
404 Handler
// Place after all routes
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
Validation
With Zod
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);
});
Authentication
JWT Authentication
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 });
});
File Uploads
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 });
});
Testing
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');
});
});
Best Practices
- Use async/await with wrapper - Catch errors properly
- Validate all input - Use Zod or Joi
- Use helmet - Security headers
- Centralize error handling - Error middleware
- Structure by feature - Not by type
Common Mistakes
| 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 |
Reference Files
- references/middleware.md - Middleware patterns
- references/security.md - Security best practices
- references/testing.md - Testing with supertest
More by mgd34msu
View allMonitors background agents efficiently using local file reads instead of TaskOutput API calls. Use when running parallel background agents, checking agent progress, detecting completion status, or minimizing token usage during multi-agent orchestration.
Provides exhaustive security vulnerability checklists with severity classifications, point deductions, and detection commands. Use when performing security audits, code reviews, penetration testing preparation, or checking OWASP compliance.
Builds GraphQL APIs with Apollo Server 4, schema design, resolvers, and data sources. Use when implementing GraphQL servers, building federated graphs, or integrating GraphQL with Node.js frameworks.
Generates README files, API documentation, changelogs, runbooks, and SDK code from project analysis. Use when creating documentation, generating changelogs, documenting APIs, creating runbooks, or generating client SDKs.
