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 allAnalyzes codebases to map architecture, dependencies, complexity metrics, and data flows for rapid onboarding. Use when exploring new repos, understanding project layout, analyzing dependencies, measuring code complexity, or preparing codebase documentation.
Builds AI-powered applications with Vercel AI SDK including streaming responses, chat interfaces, and model integration. Use when integrating LLMs, building chat applications, streaming AI responses, or implementing AI features in React.
Implements Passport.js authentication middleware with local, OAuth, and JWT strategies for Express/Node.js. Use when building Node.js APIs, implementing custom auth flows, or needing flexible authentication strategies.
Provides patterns for file structure, module boundaries, naming conventions, directory organization, and import/export patterns. Use when organizing code, creating new projects, restructuring codebases, establishing module boundaries, or reviewing project architecture.
