웹 서버는 API 응답만 처리하는 것이 아니라 HTML, CSS, JS, 이미지 같은 정적 리소스도 클라이언트에 전달해야 한다. Express는 express.static 미들웨어 하나로 이 역할을 담당하며, 별도의 파일 읽기 로직 없이 디렉토리 단위로 제공 경로를 선언할 수 있다.
express.static(root) 은 지정한 디렉토리를 정적 파일 루트로 등록하는 Built-in Middleware다.
const express = require('express');
const app = express();
app.use(express.static('public'));
app.listen(3000);
프로젝트 루트의 public 폴더 안에 파일을 두면, http://localhost:3000/style.css 요청 시 public/style.css 가 자동으로 응답된다.
Built-in Middleware — Express 4.x 이후 기본 내장된 미들웨어. 별도 패키지 설치 불필요.
관례적으로 정적 파일은 public 디렉토리 하위에 용도별로 분리한다.
project/
├── public/
│ ├── images/
│ │ └── logo.png
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── app.js
├── routes/
└── app.js
| 파일 경로 | 브라우저 요청 URL |
|---|---|
public/css/style.css | /css/style.css |
public/images/logo.png | /images/logo.png |
public/js/app.js | /js/app.js |
public 디렉토리명 자체는 URL에 포함되지 않는다. Express가 루트를 벗겨내고 하위 경로만 노출한다.
URL에 Virtual Prefix를 붙여 실제 디렉토리 구조를 숨길 수 있다.
app.use('/static', express.static('public'));
이 경우 public/css/style.css 는 /static/css/style.css 로 접근된다.
브라우저 요청: GET /static/css/style.css
↓
Express 매핑: /static → public/
↓
실제 파일: public/css/style.css
Virtual Prefix — 실제 파일 경로와 무관하게 URL 앞에 붙이는 가상 접두사. 파일 구조 은닉이나 CDN 전환 시 유용하다.
app.use 를 여러 번 호출해 복수의 디렉토리를 등록할 수 있다. 요청이 들어오면 등록 순서대로 파일을 탐색하고 처음 발견된 파일을 반환한다.
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('assets'));
GET /logo.png
│
├─ public/logo.png 존재? → 응답
├─ uploads/logo.png 존재? → 응답
└─ assets/logo.png 존재? → 응답 / 없으면 404
같은 파일명이 여러 디렉토리에 있으면 먼저 등록된 디렉토리의 파일이 우선한다.
express.static 에 상대 경로를 넘기면 Node.js 프로세스 실행 위치에 따라 경로가 달라질 수 있다. __dirname 을 사용해 항상 파일 기준 절대 경로를 전달하는 것이 안전하다.
const path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
path.join(__dirname, 'public') 은 app.js 가 위치한 디렉토리를 기준으로 public 폴더의 절대 경로를 반환한다.
__dirname — 현재 실행 중인 파일의 디렉토리 절대 경로를 담는 Node.js 전역 변수.