Express.js REST API — Complete Tutorial
Advertisement
Express.js remains the most popular Node.js framework. It's lightweight, flexible, and perfect for building REST APIs at any scale.
Setting Up Express
npm install express cors dotenv
npm install --save-dev typescript @types/express ts-node
import express, { Express } from "express";
import cors from "cors";
import dotenv from "dotenv";
dotenv.config();
const app: Express = express();
const port = process.env.PORT || 3000;
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Routing
// Simple routes
app.get("/health", (req, res) => {
res.json({ status: "OK" });
});
// Route parameters
app.get("/users/:id", (req, res) => {
const { id } = req.params;
res.json({ userId: id });
});
// Query parameters
app.get("/products", (req, res) => {
const { limit = 10, offset = 0 } = req.query;
res.json({ limit, offset });
});
// Request body
app.post("/users", (req, res) => {
const { name, email } = req.body;
res.status(201).json({ id: 1, name, email });
});
// Route handlers
const getUser = (req: any, res: any) => {
res.json({ id: 1, name: "Alice" });
};
app.get("/api/users/:id", getUser);
// Method chaining
app.route("/users/:id")
.get((req, res) => res.json({ id: req.params.id }))
.put((req, res) => res.json({ updated: true }))
.delete((req, res) => res.json({ deleted: true }));
- Setting Up Express
- Routing
- Middleware
- Controllers and Services
- Error Handling
- Validation
- Advanced Patterns
- Router Organization
- Rate Limiting
- Compression
- Testing
- FAQ
Middleware
// Logger middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
// Authentication middleware
interface AuthRequest extends express.Request {
user?: { id: number; email: string };
}
const authMiddleware = (req: AuthRequest, res: any, next: any) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "No token" });
req.user = { id: 1, email: "user@example.com" };
next();
};
app.use(authMiddleware);
// Error handling middleware (must be last)
app.use(
(err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err);
res.status(err.status || 500).json({ error: err.message });
}
);
Controllers and Services
// User service
class UserService {
async getUser(id: number): Promise<User> {
return { id, name: "Alice", email: "alice@example.com" };
}
async createUser(name: string, email: string): Promise<User> {
return { id: 1, name, email };
}
}
// User controller
class UserController {
private service = new UserService();
async getUser(req: express.Request, res: express.Response) {
const { id } = req.params;
const user = await this.service.getUser(parseInt(id));
res.json(user);
}
async createUser(req: express.Request, res: express.Response) {
const { name, email } = req.body;
const user = await this.service.createUser(name, email);
res.status(201).json(user);
}
}
// Use controller
const userController = new UserController();
app.get("/users/:id", (req, res) => userController.getUser(req, res));
app.post("/users", (req, res) => userController.createUser(req, res));
Error Handling
// Custom error class
class ApiError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
}
}
// Async error wrapper
const asyncHandler = (fn: any) => (req: any, res: any, next: any) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage
app.get(
"/users/:id",
asyncHandler(async (req: any, res: any) => {
const user = await getUser(req.params.id);
if (!user) throw new ApiError(404, "User not found");
res.json(user);
})
);
// Global error handler
app.use((err: any, req: any, res: any, next: any) => {
const status = err.statusCode || 500;
const message = err.message || "Internal server error";
res.status(status).json({ error: message });
});
Validation
import { body, param, validationResult } from "express-validator";
// Validate request
app.post(
"/users",
[
body("name").notEmpty().isString(),
body("email").isEmail(),
body("age").optional().isInt({ min: 0, max: 120 }),
],
(req: any, res: any) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { name, email, age } = req.body;
res.json({ id: 1, name, email, age });
}
);
Advanced Patterns
Router Organization
// routes/users.ts
import { Router } from "express";
const router = Router();
router.get("/", (req, res) => res.json([]));
router.get("/:id", (req, res) => res.json({ id: req.params.id }));
router.post("/", (req, res) => res.status(201).json({ id: 1 }));
router.put("/:id", (req, res) => res.json({ updated: true }));
router.delete("/:id", (req, res) => res.json({ deleted: true }));
export default router;
// app.ts
import userRoutes from "./routes/users";
app.use("/api/users", userRoutes);
Rate Limiting
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
app.use("/api/", limiter);
// Stricter limits for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: "Too many login attempts",
});
app.post("/auth/login", authLimiter, (req, res) => {
// Handle login
});
Compression
import compression from "compression";
// Compress all responses
app.use(compression());
Testing
import request from "supertest";
describe("User API", () => {
it("should get user by ID", async () => {
const res = await request(app).get("/users/1");
expect(res.status).toBe(200);
expect(res.body).toHaveProperty("id");
});
it("should create user", async () => {
const res = await request(app)
.post("/users")
.send({ name: "Alice", email: "alice@example.com" });
expect(res.status).toBe(201);
});
});
FAQ
Q: Should I use Express in 2025? A: Yes, it's battle-tested. For new projects, also consider Fastify or Hono for better performance.
Q: How do I handle file uploads in Express? A: Use multer middleware. It handles multipart form data.
Q: What's the best way to structure a large Express application? A: Use feature-based structure with routes, controllers, and services separated by domain.
Express.js remains excellent for REST APIs. Its simplicity means you focus on your business logic, not framework magic. Master it and you can build APIs that scale.
Advertisement
← Previous
Fastify vs Express — Which is Faster?