[2026] Express.js 완벽 가이드 | Node.js 웹 프레임워크

[2026] Express.js 완벽 가이드 | Node.js 웹 프레임워크

이 글의 핵심

Express.js : Node.js 웹 프레임워크. 설치 및 기본 서버·라우팅 (Routing).

들어가며

Express.js란?

Express.js는 Node.js를 위한 빠르고 간결한 웹 프레임워크입니다. 요청이 들어오면 주소(경로)와 HTTP 메서드에 따라 교통 표지판·우편 집배처럼 “이 편지는 이 핸들러로”라고 연결해 줍니다. 그 사이사이에 미들웨어를 끼우는데, 이는 검문소나 공장의 필터처럼 로그인·JSON 파싱·CORS 같은 공통 작업을 통과시키는 단계라고 이해하시면 됩니다. 특징:

  • 간결한 API: 최소한의 코드로 서버 구축
  • 미들웨어: 요청 처리 파이프라인
  • 라우팅: URL 패턴 매칭
  • 템플릿 엔진: EJS, Pug 등 지원
  • 풍부한 생태계: 수천 개의 미들웨어 사용 사례:
  • REST API 서버
  • 웹 애플리케이션
  • 마이크로서비스
  • 프록시 서버

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. 설치 및 기본 서버

설치

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 프로젝트 초기화
npm init -y
# Express 설치
npm install express
# 개발 도구 설치
npm install --save-dev nodemon

Hello World

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
    res.send('Hello, Express!');
});
const PORT = 3000;
app.listen(PORT, () => {
    console.log(`서버 실행 중: http://localhost:${PORT}`);
});

실행:

node app.js
# 또는
nodemon app.js

기본 구조

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const app = express();
// 미들웨어
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 라우트
app.get('/', (req, res) => {
    res.send('홈 페이지');
});
app.get('/about', (req, res) => {
    res.send('소개 페이지');
});
// 404 처리
app.use((req, res) => {
    res.status(404).send('페이지를 찾을 수 없습니다');
});
// 에러 처리
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('서버 에러');
});
// 서버 시작
app.listen(3000, () => {
    console.log('서버 실행 중: http://localhost:3000');
});

2. 라우팅 (Routing)

app.get('/users', …)처럼 경로 + 메서드가 맞을 때만 실행되는 함수를 붙입니다. 우편물 주소가 동네·번지·호수까지 정확히 맞아야 배달되듯, 클라이언트가 GET /api/users/42처럼 요청하면 :id42가 들어가 해당 핸들러 한 곳으로만 전달됩니다.

HTTP 메서드

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const app = express();
// GET: 데이터 조회
app.get('/users', (req, res) => {
    res.json({ users: ['홍길동', '김철수'] });
});
// POST: 데이터 생성
app.post('/users', (req, res) => {
    const user = req.body;
    res.status(201).json({ message: '사용자 생성됨', user });
});
// PUT: 데이터 전체 수정
app.put('/users/:id', (req, res) => {
    const { id } = req.params;
    const user = req.body;
    res.json({ message: `사용자 ${id} 수정됨`, user });
});
// PATCH: 데이터 일부 수정
app.patch('/users/:id', (req, res) => {
    const { id } = req.params;
    const updates = req.body;
    res.json({ message: `사용자 ${id} 일부 수정됨`, updates });
});
// DELETE: 데이터 삭제
app.delete('/users/:id', (req, res) => {
    const { id } = req.params;
    res.json({ message: `사용자 ${id} 삭제됨` });
});
// ALL: 모든 메서드
app.all('/secret', (req, res) => {
    res.send('비밀 페이지');
});

경로 매개변수 (Route Parameters)

다음은 javascript를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 단일 매개변수
app.get('/users/:id', (req, res) => {
    const { id } = req.params;
    res.send(`사용자 ID: ${id}`);
});
// GET /users/123 → id: "123"
// 여러 매개변수
app.get('/users/:userId/posts/:postId', (req, res) => {
    const { userId, postId } = req.params;
    res.json({ userId, postId });
});
// GET /users/123/posts/456 → { userId: "123", postId: "456" }
// 정규식 패턴
app.get('/files/:filename(\\d+\\.txt)', (req, res) => {
    const { filename } = req.params;
    res.send(`파일: ${filename}`);
});
// GET /files/123.txt ✅
// GET /files/abc.txt ❌
// 선택적 매개변수
app.get('/users/:id?', (req, res) => {
    if (req.params.id) {
        res.send(`사용자 ID: ${req.params.id}`);
    } else {
        res.send('모든 사용자');
    }
});

쿼리 문자열 (Query String)

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

app.get('/search', (req, res) => {
    const { q, page, limit } = req.query;
    
    res.json({
        query: q,
        page: parseInt(page) || 1,
        limit: parseInt(limit) || 10
    });
});
// GET /search?q=nodejs&page=2&limit=20
// { query: "nodejs", page: 2, limit: 20 }

라우터 분리

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// routes/users.js
const express = require('express');
const router = express.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({ message: '생성됨' });
});
module.exports = router;

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const express = require('express');
const usersRouter = require('./routes/users');
const postsRouter = require('./routes/posts');
const app = express();
app.use('/api/users', usersRouter);
app.use('/api/posts', postsRouter);
app.listen(3000);

3. 미들웨어 (Middleware)

미들웨어란?

미들웨어는 요청과 응답 사이에서 실행되는 함수입니다. 들어온 요청이 라우트 핸들러에 닿기 전에 검문·신분 확인·본문(JSON) 해석 같은 공통 절차를 거치게 할 때 씁니다. next()를 호출해야만 다음 검문소로 통과하고, 호출하지 않으면 그 자리에서 응답으로 끝납니다. 아래 코드는 javascript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

function myMiddleware(req, res, next) {
    console.log('미들웨어 실행');
    next();  // 다음 미들웨어로 이동
}
app.use(myMiddleware);

실행 흐름:

요청 → 미들웨어1 → 미들웨어2 → 라우트 핸들러 → 응답

애플리케이션 레벨 미들웨어

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const app = express();
// 모든 요청에 실행
app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    console.log('시간:', new Date().toISOString());
    next();
});
// 특정 경로에만 실행
app.use('/api', (req, res, next) => {
    console.log('API 요청');
    next();
});
// 여러 미들웨어 체이닝
app.get('/users',
    (req, res, next) => {
        console.log('미들웨어 1');
        next();
    },
    (req, res, next) => {
        console.log('미들웨어 2');
        next();
    },
    (req, res) => {
        res.send('사용자 목록');
    }
);

내장 미들웨어

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// JSON 파싱
app.use(express.json());
// URL-encoded 파싱
app.use(express.urlencoded({ extended: true }));
// 정적 파일 서빙
app.use(express.static('public'));
// public/style.css → http://localhost:3000/style.css
// 여러 정적 폴더
app.use('/static', express.static('public'));
app.use('/uploads', express.static('uploads'));

서드파티 미들웨어

npm install cors morgan helmet compression

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const cors = require('cors');
const morgan = require('morgan');
const helmet = require('helmet');
const compression = require('compression');
// CORS (Cross-Origin Resource Sharing)
app.use(cors());
// HTTP 요청 로깅
app.use(morgan('dev'));
// GET /users 200 15.234 ms - 123
// 보안 헤더
app.use(helmet());
// 응답 압축
app.use(compression());

커스텀 미들웨어

로깅 미들웨어: 아래 코드는 javascript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function logger(req, res, next) {
    const start = Date.now();
    
    res.on('finish', () => {
        const duration = Date.now() - start;
        console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
    });
    
    next();
}
app.use(logger);

인증 미들웨어: 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 함수 정의 및 구현
function authenticate(req, res, next) {
    const token = req.headers.authorization;
    
    if (!token) {
        return res.status(401).json({ error: '인증 토큰이 필요합니다' });
    }
    
    try {
        // 토큰 검증 (예: JWT)
        const user = verifyToken(token);
        req.user = user;
        next();
    } catch (err) {
        res.status(401).json({ error: '유효하지 않은 토큰' });
    }
}
// 보호된 라우트
app.get('/profile', authenticate, (req, res) => {
    res.json({ user: req.user });
});

권한 확인 미들웨어: 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function authorize(...roles) {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(401).json({ error: '인증 필요' });
        }
        
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ error: '권한 없음' });
        }
        
        next();
    };
}
// 사용
app.delete('/users/:id', 
    authenticate,
    authorize('admin'),
    (req, res) => {
        res.json({ message: '사용자 삭제됨' });
    }
);

4. REST API 구축

CRUD 구현

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const app = express();
app.use(express.json());
// 임시 데이터베이스
let users = [
    { id: 1, name: '홍길동', email: 'hong@example.com' },
    { id: 2, name: '김철수', email: 'kim@example.com' }
];
let nextId = 3;
// CREATE: 사용자 생성
app.post('/api/users', (req, res) => {
    const { name, email } = req.body;
    
    // 유효성 검사
    if (!name || !email) {
        return res.status(400).json({ error: '이름과 이메일이 필요합니다' });
    }
    
    const user = { id: nextId++, name, email };
    users.push(user);
    
    res.status(201).json(user);
});
// READ: 모든 사용자 조회
app.get('/api/users', (req, res) => {
    const { page = 1, limit = 10 } = req.query;
    
    const startIndex = (page - 1) * limit;
    const endIndex = page * limit;
    
    const paginatedUsers = users.slice(startIndex, endIndex);
    
    res.json({
        users: paginatedUsers,
        total: users.length,
        page: parseInt(page),
        totalPages: Math.ceil(users.length / limit)
    });
});
// READ: 특정 사용자 조회
app.get('/api/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const user = users.find(u => u.id === id);
    
    if (!user) {
        return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
    }
    
    res.json(user);
});
// UPDATE: 사용자 수정
app.put('/api/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const { name, email } = req.body;
    
    const userIndex = users.findIndex(u => u.id === id);
    
    if (userIndex === -1) {
        return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
    }
    
    users[userIndex] = { id, name, email };
    res.json(users[userIndex]);
});
// DELETE: 사용자 삭제
app.delete('/api/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const userIndex = users.findIndex(u => u.id === id);
    
    if (userIndex === -1) {
        return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
    }
    
    users.splice(userIndex, 1);
    res.status(204).send();  // No Content
});
app.listen(3000);

HTTP 상태 코드

코드의미사용 예
200OK성공
201Created리소스 생성
204No Content삭제 성공
400Bad Request잘못된 요청
401Unauthorized인증 필요
403Forbidden권한 없음
404Not Found리소스 없음
500Internal Server Error서버 에러

5. 요청과 응답

Request 객체

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

app.get('/demo', (req, res) => {
    // URL 매개변수
    console.log(req.params);
    
    // 쿼리 문자열
    console.log(req.query);
    
    // 요청 본문
    console.log(req.body);
    
    // 헤더
    console.log(req.headers);
    console.log(req.get('User-Agent'));
    
    // HTTP 메서드
    console.log(req.method);
    
    // URL
    console.log(req.url);
    console.log(req.path);
    console.log(req.originalUrl);
    
    // IP 주소
    console.log(req.ip);
    
    // 쿠키 (cookie-parser 필요)
    console.log(req.cookies);
    
    res.send('완료');
});

Response 객체

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

app.get('/response-demo', (req, res) => {
    // 텍스트 응답
    res.send('Hello');
    
    // JSON 응답
    res.json({ message: 'Success' });
    
    // 상태 코드 + JSON
    res.status(201).json({ created: true });
    
    // 파일 전송
    res.sendFile('/path/to/file.pdf');
    
    // 파일 다운로드
    res.download('/path/to/file.pdf', 'custom-name.pdf');
    
    // 리다이렉트
    res.redirect('/new-url');
    res.redirect(301, '/permanent-url');
    
    // 헤더 설정
    res.set('Content-Type', 'text/html');
    res.set({
        'Content-Type': 'application/json',
        'X-Custom-Header': 'value'
    });
    
    // 쿠키 설정
    res.cookie('name', 'value', { maxAge: 900000, httpOnly: true });
    
    // 쿠키 삭제
    res.clearCookie('name');
    
    // 렌더링 (템플릿 엔진)
    res.render('index', { title: '홈' });
});

6. 미들웨어 심화

에러 처리 미들웨어

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 비동기 에러 래퍼
function asyncHandler(fn) {
    return (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('사용자를 찾을 수 없습니다');
    }
    
    res.json(user);
}));
// 에러 처리 미들웨어 (맨 마지막에 배치)
app.use((err, req, res, next) => {
    console.error(err.stack);
    
    const statusCode = err.statusCode || 500;
    const message = err.message || '서버 에러';
    
    res.status(statusCode).json({
        error: {
            message,
            ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
        }
    });
});

커스텀 에러 클래스

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// errors.js
class AppError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = true;
        Error.captureStackTrace(this, this.constructor);
    }
}
class NotFoundError extends AppError {
    constructor(message = '리소스를 찾을 수 없습니다') {
        super(message, 404);
    }
}
class ValidationError extends AppError {
    constructor(message = '유효하지 않은 입력') {
        super(message, 400);
    }
}
module.exports = { AppError, NotFoundError, ValidationError };

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 사용
const { NotFoundError, ValidationError } = require('./errors');
app.get('/users/:id', async (req, res, next) => {
    try {
        const user = await User.findById(req.params.id);
        
        if (!user) {
            throw new NotFoundError('사용자를 찾을 수 없습니다');
        }
        
        res.json(user);
    } catch (err) {
        next(err);
    }
});

7. 실전 예제

예제 1: 블로그 API

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const express = require('express');
const app = express();
app.use(express.json());
// 데이터
let posts = [
    { id: 1, title: '첫 글', content: '내용', author: '홍길동', createdAt: new Date() }
];
let nextId = 2;
// 모든 글 조회
app.get('/api/posts', (req, res) => {
    const { author, sort = 'desc' } = req.query;
    
    let result = [...posts];
    
    // 필터링
    if (author) {
        result = result.filter(p => p.author === author);
    }
    
    // 정렬
    result.sort((a, b) => {
        return sort === 'asc' 
            ? a.createdAt - b.createdAt 
            : b.createdAt - a.createdAt;
    });
    
    res.json({ posts: result, total: result.length });
});
// 특정 글 조회
app.get('/api/posts/:id', (req, res) => {
    const post = posts.find(p => p.id === parseInt(req.params.id));
    
    if (!post) {
        return res.status(404).json({ error: '글을 찾을 수 없습니다' });
    }
    
    res.json(post);
});
// 글 작성
app.post('/api/posts', (req, res) => {
    const { title, content, author } = req.body;
    
    if (!title || !content || !author) {
        return res.status(400).json({ 
            error: '제목, 내용, 작성자가 필요합니다' 
        });
    }
    
    const post = {
        id: nextId++,
        title,
        content,
        author,
        createdAt: new Date()
    };
    
    posts.push(post);
    res.status(201).json(post);
});
// 글 수정
app.put('/api/posts/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const { title, content } = req.body;
    
    const postIndex = posts.findIndex(p => p.id === id);
    
    if (postIndex === -1) {
        return res.status(404).json({ error: '글을 찾을 수 없습니다' });
    }
    
    posts[postIndex] = {
        ...posts[postIndex],
        title,
        content,
        updatedAt: new Date()
    };
    
    res.json(posts[postIndex]);
});
// 글 삭제
app.delete('/api/posts/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const postIndex = posts.findIndex(p => p.id === id);
    
    if (postIndex === -1) {
        return res.status(404).json({ error: '글을 찾을 수 없습니다' });
    }
    
    posts.splice(postIndex, 1);
    res.status(204).send();
});
app.listen(3000, () => {
    console.log('블로그 API 서버 실행 중: http://localhost:3000');
});

예제 2: 파일 업로드

npm install multer

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// 저장 설정
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, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
    }
});
// 파일 필터
const 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('이미지 파일만 업로드 가능합니다'));
    }
};
const upload = multer({
    storage,
    fileFilter,
    limits: { fileSize: 5 * 1024 * 1024 }  // 5MB
});
// 단일 파일 업로드
app.post('/upload', upload.single('image'), (req, res) => {
    if (!req.file) {
        return res.status(400).json({ error: '파일이 필요합니다' });
    }
    
    res.json({
        message: '업로드 성공',
        file: {
            filename: req.file.filename,
            size: req.file.size,
            path: req.file.path
        }
    });
});
// 여러 파일 업로드
app.post('/upload-multiple', upload.array('images', 5), (req, res) => {
    if (!req.files || req.files.length === 0) {
        return res.status(400).json({ error: '파일이 필요합니다' });
    }
    
    res.json({
        message: `${req.files.length}개 파일 업로드 성공`,
        files: req.files.map(f => ({
            filename: f.filename,
            size: f.size
        }))
    });
});
// 에러 처리
app.use((err, req, res, next) => {
    if (err instanceof multer.MulterError) {
        if (err.code === 'LIMIT_FILE_SIZE') {
            return res.status(400).json({ error: '파일 크기가 너무 큽니다' });
        }
    }
    
    res.status(500).json({ error: err.message });
});
app.listen(3000);

예제 3: 인증 시스템

npm install bcrypt jsonwebtoken

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
const JWT_SECRET = 'your-secret-key';
const users = [];
// 회원가입
app.post('/auth/register', async (req, res) => {
    try {
        const { email, password, name } = req.body;
        
        // 유효성 검사
        if (!email || !password || !name) {
            return res.status(400).json({ error: '모든 필드가 필요합니다' });
        }
        
        // 중복 확인
        if (users.find(u => u.email === email)) {
            return res.status(400).json({ error: '이미 존재하는 이메일입니다' });
        }
        
        // 비밀번호 해싱
        const hashedPassword = await bcrypt.hash(password, 10);
        
        const user = {
            id: users.length + 1,
            email,
            password: hashedPassword,
            name,
            createdAt: new Date()
        };
        
        users.push(user);
        
        // 비밀번호 제외하고 응답
        const { password: _, ...userWithoutPassword } = user;
        res.status(201).json(userWithoutPassword);
        
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});
// 로그인
app.post('/auth/login', async (req, res) => {
    try {
        const { email, password } = req.body;
        
        // 사용자 찾기
        const user = users.find(u => u.email === email);
        
        if (!user) {
            return res.status(401).json({ error: '이메일 또는 비밀번호가 잘못되었습니다' });
        }
        
        // 비밀번호 확인
        const isValid = await bcrypt.compare(password, user.password);
        
        if (!isValid) {
            return res.status(401).json({ error: '이메일 또는 비밀번호가 잘못되었습니다' });
        }
        
        // JWT 토큰 생성
        const token = jwt.sign(
            { id: user.id, email: user.email },
            JWT_SECRET,
            { expiresIn: '1h' }
        );
        
        res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
        
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});
// 인증 미들웨어
function authenticate(req, res, next) {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ error: '인증 토큰이 필요합니다' });
    }
    
    const token = authHeader.substring(7);
    
    try {
        const decoded = jwt.verify(token, JWT_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(401).json({ error: '유효하지 않은 토큰' });
    }
}
// 보호된 라우트
app.get('/auth/profile', authenticate, (req, res) => {
    const user = users.find(u => u.id === req.user.id);
    
    if (!user) {
        return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
    }
    
    const { password, ...userWithoutPassword } = user;
    res.json(userWithoutPassword);
});
app.listen(3000);

8. 템플릿 엔진 (EJS)

설치 및 설정

npm install ejs

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const app = express();
// 뷰 엔진 설정
app.set('view engine', 'ejs');
app.set('views', './views');
app.get('/', (req, res) => {
    res.render('index', {
        title: '홈 페이지',
        message: 'Express + EJS'
    });
});
app.listen(3000);

EJS 템플릿

다음은 html를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
</head>
<body>
    <h1><%= message %></h1>
    
    <% if (users && users.length > 0) { %>
        <ul>
            <% users.forEach(user => { %>
                <li><%= user.name %> (<%= user.email %>)</li>
            <% }); %>
        </ul>
    <% } else { %>
        <p>사용자가 없습니다.</p>
    <% } %>
</body>
</html>

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

app.get('/users', (req, res) => {
    const users = [
        { name: '홍길동', email: 'hong@example.com' },
        { name: '김철수', email: 'kim@example.com' }
    ];
    
    res.render('index', {
        title: '사용자 목록',
        message: '등록된 사용자',
        users
    });
});

9. 보안

기본 보안 설정

npm install helmet cors express-rate-limit

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const app = express();
// Helmet: 보안 헤더 설정
app.use(helmet());
// CORS 설정
app.use(cors({
    origin: 'https://yourdomain.com',
    credentials: true
}));
// Rate Limiting
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,  // 15분
    max: 100,  // 최대 100개 요청
    message: '너무 많은 요청이 발생했습니다'
});
app.use('/api/', limiter);
// XSS 방지
app.use(express.json({ limit: '10kb' }));
// SQL Injection 방지 (파라미터 검증)
app.get('/users/:id', (req, res) => {
    const id = parseInt(req.params.id);
    
    if (isNaN(id)) {
        return res.status(400).json({ error: '유효하지 않은 ID' });
    }
    
    // ...
});
app.listen(3000);

입력 검증

npm install express-validator

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const { body, validationResult } = require('express-validator');
app.post('/api/users',
    // 검증 규칙
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 }),
    body('name').trim().notEmpty(),
    
    // 핸들러
    (req, res) => {
        const errors = validationResult(req);
        
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }
        
        // 유효한 데이터 처리
        res.json({ message: '사용자 생성됨' });
    }
);

10. 프로덕션 배포

환경 설정

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// config.js
module.exports = {
    port: process.env.PORT || 3000,
    nodeEnv: process.env.NODE_ENV || 'development',
    isDevelopment: process.env.NODE_ENV === 'development',
    isProduction: process.env.NODE_ENV === 'production'
};

프로덕션 설정

다음은 javascript를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const express = require('express');
const config = require('./config');
const app = express();
// 프로덕션 전용 설정
if (config.isProduction) {
    // 신뢰할 수 있는 프록시 설정
    app.set('trust proxy', 1);
    
    // 압축
    const compression = require('compression');
    app.use(compression());
    
    // 로깅
    const morgan = require('morgan');
    app.use(morgan('combined'));
}
// 개발 전용 설정
if (config.isDevelopment) {
    const morgan = require('morgan');
    app.use(morgan('dev'));
}
app.listen(config.port);

PM2로 배포

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# PM2 설치
npm install -g pm2
# 앱 시작
pm2 start app.js --name "my-app"
# 클러스터 모드 (멀티 코어 활용)
pm2 start app.js -i max
# 상태 확인
pm2 status
pm2 logs
pm2 monit
# 재시작
pm2 restart my-app
# 중지
pm2 stop my-app
# 삭제
pm2 delete my-app
# 부팅 시 자동 시작
pm2 startup
pm2 save

Nginx 리버스 프록시

다음은 nginx를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name yourdomain.com;
    
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

11. 자주 발생하는 문제

문제 1: Cannot set headers after they are sent

원인: 응답을 두 번 보냄 다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 코드
app.get('/users/:id', (req, res) => {
    const user = users.find(u => u.id === parseInt(req.params.id));
    
    if (!user) {
        res.status(404).json({ error: '없음' });
    }
    
    res.json(user);  // 에러! (404 응답 후 또 응답)
});
// ✅ return 사용
app.get('/users/:id', (req, res) => {
    const user = users.find(u => u.id === parseInt(req.params.id));
    
    if (!user) {
        return res.status(404).json({ error: '없음' });
    }
    
    res.json(user);
});

문제 2: 미들웨어 순서

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 순서
app.get('/users', (req, res) => {
    res.json({ users: [] });
});
app.use(express.json());  // 너무 늦음!
// ✅ 올바른 순서
app.use(express.json());  // 먼저
app.get('/users', (req, res) => {
    console.log(req.body);  // 파싱됨
    res.json({ users: [] });
});

문제 3: next() 호출 누락

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ next() 없음
app.use((req, res, next) => {
    console.log('미들웨어');
    // next()를 호출하지 않으면 여기서 멈춤
});
// ✅ next() 호출
app.use((req, res, next) => {
    console.log('미들웨어');
    next();  // 다음으로 진행
});

12. 실전 팁

프로젝트 구조

다음은 code를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

my-express-app/
├── src/
│   ├── config/
│   │   └── database.js
│   ├── controllers/
│   │   ├── userController.js
│   │   └── postController.js
│   ├── middlewares/
│   │   ├── auth.js
│   │   └── errorHandler.js
│   ├── models/
│   │   ├── User.js
│   │   └── Post.js
│   ├── routes/
│   │   ├── userRoutes.js
│   │   └── postRoutes.js
│   ├── utils/
│   │   └── logger.js
│   └── app.js
├── public/
├── views/
├── .env
├── .gitignore
├── package.json
└── server.js

컨트롤러 패턴

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// controllers/userController.js
const User = require('../models/User');
exports.getAllUsers = async (req, res, next) => {
    try {
        const users = await User.find();
        res.json({ users });
    } catch (err) {
        next(err);
    }
};
exports.getUserById = async (req, res, next) => {
    try {
        const user = await User.findById(req.params.id);
        
        if (!user) {
            return res.status(404).json({ error: '사용자 없음' });
        }
        
        res.json(user);
    } catch (err) {
        next(err);
    }
};
exports.createUser = async (req, res, next) => {
    try {
        const user = await User.create(req.body);
        res.status(201).json(user);
    } catch (err) {
        next(err);
    }
};

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// routes/userRoutes.js
// 변수 선언 및 초기화
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
router.post('/', userController.createUser);
module.exports = router;
// app.js
const userRoutes = require('./routes/userRoutes');
app.use('/api/users', userRoutes);

API 버전 관리

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// v1/routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
    res.json({ version: 'v1', users: [] });
});
module.exports = router;

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// v2/routes/users.js
// 변수 선언 및 초기화
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
    res.json({ version: 'v2', users: [], meta: {} });
});
module.exports = router;

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
// 변수 선언 및 초기화
const v1Users = require('./v1/routes/users');
const v2Users = require('./v2/routes/users');
app.use('/api/v1/users', v1Users);
app.use('/api/v2/users', v2Users);

정리

핵심 요약

  1. Express.js: Node.js 웹 프레임워크
  2. 라우팅: HTTP 메서드 + URL 패턴
  3. 미들웨어: 요청 처리 파이프라인
  4. REST API: CRUD 작업 구현
  5. 에러 처리: 에러 미들웨어, 비동기 래퍼
  6. 보안: Helmet, CORS, Rate Limiting
  7. 배포: PM2, Nginx

Express vs 다른 프레임워크

프레임워크특징사용 시기
Express간결, 생태계 큼일반적인 웹 앱
Fastify빠름, 스키마 검증고성능 API
Koa최신 문법, 가벼움모던 프로젝트
NestJSTypeScript, 구조화대규모 엔터프라이즈

다음 단계

추천 학습 자료

공식 문서:


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3