Build robust, scalable APIs with Node.js and Express using these modern patterns.
Project Structure
src/
├── routes/
├── controllers/
├── middleware/
├── services/
└── utils/
Express Setup
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Route Handler
import { Router } from 'express';
const router = Router();
router.get('/users', async (req, res) => {
const users = await userService.findAll();
res.json(users);
});
router.get('/users/:id', async (req, res) => {
const user = await userService.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
export default router;
Error Handling Middleware
import { Request, Response, NextFunction } from 'express';
export function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
}
Input Validation
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
password: z.string().min(8),
});
router.post('/users', async (req, res) => {
const result = createUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
const user = await userService.create(result.data);
res.status(201).json(user);
});
Best Practices
- Use TypeScript for type safety
- Validate input with Zod or Joi
- Handle errors with middleware
- Use async/await for cleaner code
- Rate limit your endpoints