Express.js Complete Guide | Fast Node.js Web Framework
이 글의 핵심
Express.js is the most popular web framework for Node.js. It provides a minimal, flexible, and robust foundation for building web applications and REST APIs.
Introduction
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It’s the de facto standard for Node.js backends.
Why Express?
Native Node.js HTTP:
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/users' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ users: [] }));
}
// ... manual routing for every endpoint
}).listen(3000);
With Express:
const express = require('express');
const app = express();
app.get('/users', (req, res) => {
res.json({ users: [] });
});
app.listen(3000);
1. Installation
npm install express
# TypeScript
npm install express
npm install -D @types/express typescript ts-node
2. Basic Server
const express = require('express');
const app = express();
// Middleware
app.use(express.json()); // Parse JSON bodies
// Routes
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.get('/api/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
3. Routing
Basic Routes
// GET request
app.get('/users', (req, res) => {
res.json({ users: [] });
});
// POST request
app.post('/users', (req, res) => {
const user = req.body;
res.status(201).json({ user });
});
// PUT request
app.put('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `Updated user ${id}` });
});
// DELETE request
app.delete('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `Deleted user ${id}` });
});
// Multiple methods
app.route('/users/:id')
.get((req, res) => { /* ... */ })
.put((req, res) => { /* ... */ })
.delete((req, res) => { /* ... */ });
Route Parameters
// URL params
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) => {
const { id } = req.params;
if (id) {
res.json({ user: { id } });
} else {
res.json({ users: [] });
}
});
Query Parameters
// /users?page=1&limit=10
app.get('/users', (req, res) => {
const { page = 1, limit = 10 } = req.query;
res.json({ page, limit });
});
Router
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.post('/', (req, res) => {
res.status(201).json({ user: req.body });
});
router.get('/:id', (req, res) => {
res.json({ user: { id: req.params.id } });
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);
4. Middleware
Built-in Middleware
// 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'));
// Serve static files with prefix
app.use('/static', express.static('public'));
Custom Middleware
// Logger middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // Pass to next middleware
});
// Timing middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
});
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
// Verify token
try {
req.user = verifyToken(token);
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Use authentication
app.get('/api/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
Third-Party Middleware
npm install cors helmet morgan compression cookie-parser
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
const cookieParser = require('cookie-parser');
// Security headers
app.use(helmet());
// CORS
app.use(cors({
origin: 'https://example.com',
credentials: true,
}));
// Logging
app.use(morgan('combined'));
// Compression
app.use(compression());
// Cookie parser
app.use(cookieParser());
// Body parsers
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
5. Error Handling
// 404 handler (must be after all routes)
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
});
// Error handler (must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: {
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
});
});
// Async error handling
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new Error('User not found');
}
res.json({ user });
}));
6. Request and Response
Request Object
app.get('/example', (req, res) => {
// URL params
const { id } = req.params;
// Query params
const { page, limit } = req.query;
// Body
const data = req.body;
// Headers
const contentType = req.get('Content-Type');
const auth = req.headers.authorization;
// Cookies
const sessionId = req.cookies.sessionId;
// IP
const ip = req.ip;
// Path
const path = req.path;
// Method
const method = req.method;
});
Response Methods
// JSON response
res.json({ message: 'Success' });
// Text response
res.send('Hello World');
// Status code
res.status(201).json({ created: true });
// Redirect
res.redirect('/login');
res.redirect(301, '/new-url');
// Download file
res.download('/files/document.pdf');
// Send file
res.sendFile('/path/to/file.html');
// Set headers
res.set('Content-Type', 'application/json');
res.set({
'Content-Type': 'application/json',
'X-Custom-Header': 'value',
});
// Cookies
res.cookie('sessionId', '123', { httpOnly: true, maxAge: 3600000 });
res.clearCookie('sessionId');
7. Authentication Example
npm install bcrypt jsonwebtoken
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Register
app.post('/api/register', async (req, res) => {
try {
const { email, password } = req.body;
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Save user (example)
const user = await db.users.create({
email,
password: hashedPassword,
});
res.status(201).json({ message: 'User created' });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Login
app.post('/api/login', async (req, res) => {
try {
const { email, password } = req.body;
// Find user
const user = await db.users.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify password
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = jwt.sign(
{ userId: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Authentication middleware
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1]; // Bearer <token>
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Protected route
app.get('/api/profile', authenticate, async (req, res) => {
const user = await db.users.findById(req.user.userId);
res.json({ user });
});
8. File Upload
npm install multer
const multer = require('multer');
const path = require('path');
// Configure storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, '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 allowedTypes = /jpeg|jpg|png|gif/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (extname && mimetype) {
cb(null, true);
} else {
cb(new Error('Only images allowed'));
}
},
});
// Single file
app.post('/api/upload', upload.single('image'), (req, res) => {
res.json({ file: req.file });
});
// Multiple files
app.post('/api/upload-multiple', upload.array('images', 10), (req, res) => {
res.json({ files: req.files });
});
9. TypeScript Integration
// src/app.ts
import express, { Request, Response, NextFunction } from 'express';
const app = express();
interface User {
id: string;
email: string;
}
// Extend Request type
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
app.get('/users/:id', async (req: Request, res: Response) => {
const { id } = req.params;
const user = await findUser(id);
res.json({ user });
});
// Typed middleware
const authenticate = (req: Request, res: Response, next: NextFunction) => {
// Authenticate and set req.user
next();
};
app.get('/profile', authenticate, (req: Request, res: Response) => {
res.json({ user: req.user });
});
app.listen(3000);
10. Validation
npm install express-validator
const { body, validationResult } = require('express-validator');
app.post(
'/api/users',
[
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
body('age').isInt({ min: 0, max: 120 }),
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process valid data
res.json({ message: 'User created' });
}
);
11. Best Practices
1. Environment Variables
require('dotenv').config();
const PORT = process.env.PORT || 3000;
const DB_URL = process.env.DATABASE_URL;
const JWT_SECRET = process.env.JWT_SECRET;
2. Structured Folder Layout
src/
├── controllers/
│ └── userController.js
├── routes/
│ └── users.js
├── middleware/
│ ├── auth.js
│ └── errorHandler.js
├── models/
│ └── User.js
├── config/
│ └── database.js
└── app.js
3. Error Handling
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
Error.captureStackTrace(this, this.constructor);
}
}
const errorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
res.status(err.statusCode).json({
status: err.status,
message: err.message,
});
};
app.use(errorHandler);
4. Rate Limiting
npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests',
});
app.use('/api/', limiter);
12. Production Setup
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
// Security
app.use(helmet());
// CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true,
}));
// Compression
app.use(compression());
// Logging
app.use(morgan('combined'));
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Trust proxy (for Nginx, etc.)
app.set('trust proxy', 1);
// Routes
app.use('/api', apiRouter);
// Error handling
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Summary
Express.js is the foundation for Node.js backends:
- Minimal and unopinionated
- Flexible routing and middleware
- Robust ecosystem
- Battle-tested in production
- TypeScript support
Key Takeaways:
- Use middleware for cross-cutting concerns
- Organize routes with Router
- Handle errors centrally
- Validate input data
- Secure with Helmet and CORS
Next Steps:
- Learn Node.js
- Compare NestJS
- Deploy with PM2
Resources: