Express.js Complete Guide | Fast Node.js Web Framework

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:

  1. Use middleware for cross-cutting concerns
  2. Organize routes with Router
  3. Handle errors centrally
  4. Validate input data
  5. Secure with Helmet and CORS

Next Steps:

  • Learn Node.js
  • Compare NestJS
  • Deploy with PM2

Resources: