서버가 파일을 직접 건네주는 순간
지금까지 서버는 짧은 문장이나 JSON 데이터를 응답했습니다. 예를 들어 res.send('안녕하세요')처럼 서버 코드 안에 직접 문장을 적어 보냈습니다.
하지만 실제 웹사이트는 보통 한 줄 문장으로 끝나지 않습니다. HTML 파일이 있고, 글자를 꾸미는 CSS 파일이 있고, 로고나 사진 같은 이미지 파일도 있습니다. 버튼을 눌렀을 때 움직임을 만드는 브라우저용 JavaScript 파일도 따로 둘 수 있습니다.
이런 파일들은 요청이 올 때마다 새로 계산해서 만드는 것이 아니라, 이미 준비된 파일을 그대로 보내면 됩니다. 마치 안내 데스크 직원이 매번 안내문을 새로 쓰지 않고, 미리 인쇄해 둔 종이를 꺼내 주는 것과 같습니다. 이렇게 서버가 그대로 제공하는 파일을 정적 파일이라고 부릅니다.
이번 장에서는 Express 서버가 HTML, CSS, 이미지, 브라우저용 JavaScript 파일을 브라우저에 전달하도록 만들어 보겠습니다.
AI 생성 이미지
이번에 만들 것
작은 예약 안내 페이지를 만들겠습니다.
브라우저에서 http://localhost:3000으로 들어가면 HTML 페이지가 보입니다. 그 HTML은 CSS 파일을 불러와 색과 여백을 입고, 이미지 파일을 불러와 로고를 보여 줍니다. 버튼을 누르면 브라우저용 JavaScript 파일이 작동해 안내 문구가 나타납니다.
완성 후에는 이런 흐름을 이해하게 됩니다.
/주소로 들어오면 서버가 HTML 파일을 보낸다.HTML 안의
/css/style.css경로를 보고 브라우저가 CSS 파일을 다시 요청한다.HTML 안의
/images/logo.svg경로를 보고 브라우저가 이미지 파일을 다시 요청한다.HTML 안의
/js/main.js경로를 보고 브라우저가 JavaScript 파일을 다시 요청한다.Express의
express.static()이 이 파일들을 찾아서 보내 준다.
폴더를 먼저 정리하기
먼저 무엇을 만들고 있는지 분명히 하겠습니다. 서버 코드와 브라우저가 직접 받아 갈 파일을 섞어 두지 않겠습니다.
프로젝트 폴더 express-practice 안을 다음처럼 정리합니다.
express-practice/
├─ app.js
├─ views/
│ └─ home.html
└─ public/
├─ css/
│ └─ style.css
├─ images/
│ └─ logo.svg
└─ js/
└─ main.js역할은 간단합니다.
app.js는 서버 코드입니다. 어떤 주소로 들어왔을 때 무엇을 보낼지 정합니다.
views 폴더에는 HTML 파일을 둡니다. 여기서는 home.html을 만들겠습니다.
public 폴더에는 브라우저가 직접 요청해 가져갈 파일을 둡니다. CSS, 이미지, 브라우저용 JavaScript가 여기에 들어갑니다.
VS Code에서 express-practice 폴더를 열고 터미널을 엽니다. 폴더를 명령으로 만들고 싶다면 다음을 입력합니다.
macOS나 Linux 터미널에서는 다음 명령을 사용할 수 있습니다.
mkdir -p views public/css public/images public/jsWindows PowerShell에서는 다음처럼 입력해도 됩니다.
mkdir views, public/css, public/images, public/js명령이 낯설다면 VS Code 왼쪽 탐색기에서 직접 폴더를 만들어도 괜찮습니다. 중요한 것은 폴더 이름이 정확해야 한다는 점입니다.
HTML 파일 만들기
이제 어떤 파일을 열지 정하겠습니다. views 폴더 안에 home.html 파일을 만듭니다.
먼저 HTML의 기본 뼈대를 넣습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>작은 예약 안내</title>
</head>
<body>
<h1>작은 예약 접수 서버</h1>
<p>Express가 HTML 파일을 보내고 있습니다.</p>
</body>
</html>여기까지는 순수한 HTML입니다. 아직 CSS도 없고 이미지도 없습니다.
이제 CSS 파일을 연결하겠습니다. <head> 안에 다음 줄을 추가합니다.
<link rel="stylesheet" href="/css/style.css">이 줄은 브라우저에게 이렇게 말합니다.
"이 페이지를 꾸미려면 서버의 /css/style.css 주소로 가서 CSS 파일을 받아 와."
주의할 점이 있습니다. 실제 파일은 public/css/style.css에 있지만, HTML에는 /public/css/style.css라고 쓰지 않습니다. 곧 Express에게 public 폴더를 정적 파일 보관함으로 알려 줄 것이기 때문입니다. 그 뒤에는 브라우저가 /css/style.css라고 요청하면 Express가 public/css/style.css 파일을 찾아 줍니다.
이제 본문을 조금 더 꾸며 보겠습니다. body 안을 다음처럼 바꿉니다.
<header class="hero">
<img src="/images/logo.svg" alt="예약 로고" class="logo">
<h1>작은 예약 접수 서버</h1>
<p>Express가 HTML, CSS, 이미지 파일을 함께 전달하고 있습니다.</p>
</header>
<main class="card">
<h2>오늘의 안내</h2>
<p>이 페이지는 서버가 파일을 보내서 만들어진 웹페이지입니다.</p>
<button id="helloButton">안내 문구 보기</button>
<p id="message"></p>
</main>
<script src="/js/main.js"></script>/images/logo.svg는 이미지 파일 주소입니다. /js/main.js는 브라우저에서 실행될 JavaScript 파일 주소입니다.
이 주소들은 컴퓨터 폴더 주소가 아니라, 브라우저가 서버에 요청하는 URL 경로입니다. 이 차이를 천천히 익히면 정적 파일 경로에서 덜 헤매게 됩니다.
CSS 파일 만들기
이번에는 public/css/style.css 파일을 엽니다.
CSS는 HTML에 옷을 입히는 역할을 합니다. 아래 코드를 넣습니다.
body {
margin: 0;
font-family: Arial, sans-serif;
background: #f6f7fb;
color: #222;
}
.hero {
padding: 40px 20px;
text-align: center;
background: #2457c5;
color: white;
}
.logo {
width: 80px;
height: 80px;
}
.card {
max-width: 520px;
margin: 30px auto;
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
}
button {
padding: 10px 14px;
border: 0;
border-radius: 8px;
background: #2457c5;
color: white;
cursor: pointer;
}CSS 파일은 서버 코드가 아닙니다. Node.js가 실행하는 파일도 아닙니다. 브라우저가 내려받아 해석하는 파일입니다.
이미지 파일 만들기
이번 실습에서는 그림 파일을 직접 만들기 쉽게 SVG를 사용하겠습니다. SVG도 브라우저가 보여 줄 수 있는 이미지 파일입니다.
public/images/logo.svg 파일을 열고 다음 내용을 넣습니다.
<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<rect width="120" height="120" rx="24" fill="#ffffff"/>
<circle cx="60" cy="48" r="22" fill="#2457c5"/>
<rect x="34" y="74" width="52" height="14" rx="7" fill="#2457c5"/>
</svg>이미 준비한 PNG 파일이 있다면 public/images/logo.png처럼 저장해도 됩니다. 그 경우 HTML의 이미지 경로를 /images/logo.png로 바꾸면 됩니다.
핵심은 파일 종류보다 위치입니다. 이미지가 public/images 안에 있고, HTML에서는 /images/파일이름으로 요청한다는 점을 기억하면 됩니다.
브라우저용 JavaScript 파일 만들기
이제 public/js/main.js 파일을 엽니다.
이 파일은 서버에서 실행되는 JavaScript가 아닙니다. 브라우저가 내려받은 뒤, 사용자의 화면 안에서 실행하는 JavaScript입니다.
다음 코드를 넣습니다.
const button = document.querySelector('#helloButton');
const message = document.querySelector('#message');
button.addEventListener('click', () => {
message.textContent = '브라우저용 JavaScript 파일도 public 폴더에서 왔습니다.';
});첫 줄은 HTML에서 id="helloButton"인 버튼을 찾습니다. 둘째 줄은 id="message"인 문단을 찾습니다. 버튼을 클릭하면 문단 안에 안내 문구를 넣습니다.
Express에 public 폴더 알려 주기
이제 서버가 이 파일들을 보낼 수 있게 해야 합니다. app.js 파일을 엽니다.
이번 장 실습에서는 app.js를 잠시 단순하게 정리해 보겠습니다.
먼저 Express를 불러옵니다.
const express = require('express');HTML 파일의 정확한 위치를 만들기 위해 Node.js의 path도 불러옵니다.
const path = require('path');path는 운영체제마다 조금씩 다른 폴더 경로 표기를 안전하게 이어 주는 도구입니다. Windows와 macOS의 길 표시 방식이 달라도 코드가 덜 흔들리게 도와줍니다.
이제 Express 앱과 포트 번호를 준비합니다.
const app = express();
const PORT = 3000;다음 줄이 이번 장의 핵심입니다.
app.use(express.static(path.join(__dirname, 'public')));이 줄은 Express에게 이렇게 알려 줍니다.
"public 폴더 안의 파일은 브라우저가 직접 요청할 수 있게 해 줘."
__dirname은 현재 실행 중인 파일, 여기서는 app.js가 들어 있는 폴더를 가리킵니다. path.join(__dirname, 'public')은 그 폴더 안의 public 폴더를 정확히 가리키는 표현입니다.
이제 / 주소로 들어왔을 때 HTML 파일을 보내겠습니다.
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'views', 'home.html'));
});res.sendFile()은 파일 하나를 응답으로 보내는 메서드입니다. 이전에 사용한 res.send()가 짧은 문장이나 작은 HTML 조각을 코드 안에서 직접 보내는 방식이었다면, res.sendFile()은 이미 만들어 둔 파일을 통째로 보내는 방식입니다.
마지막으로 서버를 켭니다.
app.listen(PORT, () => {
console.log(`서버 실행: http://localhost:${PORT}`);
});서버 코드가 바뀌었으므로 터미널에서 서버를 다시 실행합니다.
node app.js성공하면 터미널에 다음과 비슷한 문장이 보입니다.
서버 실행: http://localhost:3000브라우저에서 http://localhost:3000으로 접속합니다. 파란색 영역, 로고 이미지, 흰색 카드, 버튼이 보이면 성공입니다. 버튼을 눌렀을 때 안내 문구가 나타나면 JavaScript 파일도 잘 전달된 것입니다.
주소와 파일 위치가 연결되는 방식
여기서 가장 헷갈리기 쉬운 부분을 다시 정리하겠습니다.
HTML에는 이렇게 적었습니다.
<link rel="stylesheet" href="/css/style.css">브라우저는 이 줄을 보고 서버에 이렇게 요청합니다.
GET /css/style.cssExpress는 express.static()으로 등록된 public 폴더 안에서 해당 파일을 찾습니다.
public/css/style.css그래서 HTML에서는 /css/style.css라고 쓰지만, 실제 프로젝트 폴더에서는 public/css/style.css에 파일이 있어야 합니다.
이미지도 같습니다.
<img src="/images/logo.svg">브라우저 요청은 /images/logo.svg이고, Express가 찾는 실제 파일은 public/images/logo.svg입니다.
직접 확인하고 싶다면 브라우저 주소창에 다음 주소들을 입력해 보세요.
http://localhost:3000/css/style.css
http://localhost:3000/images/logo.svg
http://localhost:3000/js/main.jsCSS 주소에서는 CSS 코드가 보이고, 이미지 주소에서는 로고가 보이고, JavaScript 주소에서는 JavaScript 코드가 보이면 정적 파일 제공이 잘 작동하고 있는 것입니다.
정적 파일과 서버 코드는 분리해 두기
public 폴더는 브라우저가 직접 가져갈 수 있는 공개 보관함입니다. 그래서 이름도 흔히 public이라고 짓습니다.
반대로 서버 코드, 설정 파일, 비밀번호나 API 키가 들어 있는 파일은 public 안에 두면 안 됩니다. public 안에 둔 파일은 주소만 알면 브라우저로 요청할 수 있기 때문입니다.
처음에는 작은 프로젝트라서 모든 파일을 한곳에 두고 싶을 수 있습니다. 하지만 다음 기준으로 나누면 프로젝트가 커져도 길을 잃을 가능성이 줄어듭니다.
app.js: 서버 실행과 라우팅 코드views: 서버가 특정 상황에서 보내는 HTML 파일public/css: CSS 파일public/images: 이미지 파일public/js: 브라우저에서 실행할 JavaScript 파일
서버가 일하는 공간과 브라우저에게 공개할 공간을 나누는 습관은 이후 프로젝트 정리에도 그대로 이어집니다.
CSS나 이미지가 보이지 않을 때 점검하기
정적 파일 실습에서 화면이 기대와 다르게 보이는 일은 자주 있습니다. 오류가 났다는 뜻이 아니라, 서버와 브라우저가 약속한 경로 중 어딘가가 어긋났다는 신호입니다.
먼저 경로를 확인합니다. HTML에 /css/style.css라고 썼다면 실제 파일은 public/css/style.css에 있어야 합니다. /images/logo.svg라고 썼다면 실제 파일은 public/images/logo.svg에 있어야 합니다.
둘째, 파일 이름의 대소문자를 확인합니다. 내 컴퓨터에서는 우연히 보일 때도 있지만, 인터넷 서버에 올렸을 때는 Logo.svg와 logo.svg를 다른 파일로 보는 경우가 많습니다. HTML에 쓴 이름과 실제 파일 이름을 글자 하나까지 맞추는 습관이 좋습니다.
셋째, app.use(express.static(...)) 코드가 있는지 확인합니다. 이 코드가 없으면 Express는 public 폴더를 공개 보관함으로 알지 못합니다.
넷째, 서버 코드인 app.js를 고쳤다면 서버를 껐다가 다시 켭니다. 터미널에서 Ctrl + C로 종료한 뒤 다시 실행합니다.
node app.jsCSS나 HTML 파일만 고친 경우에는 브라우저 새로고침으로 반영되는 경우가 많습니다. 그래도 예전 화면이 계속 보이면 강력 새로고침을 해 보세요. Windows에서는 보통 Ctrl + F5, macOS에서는 브라우저에 따라 Cmd + Shift + R을 사용합니다.
다섯째, 404 처리 미들웨어의 위치를 확인합니다. 이전 장에서 404 응답을 app.use()로 만들었다면, 보통 정적 파일 등록과 주요 라우트보다 아래에 두어야 합니다. Express는 위에서 아래로 코드를 확인하기 때문입니다.
이번 장에서 기억할 것
서버는 데이터만 보내는 프로그램이 아닙니다. HTML 파일을 보내 웹페이지를 보여 줄 수도 있고, 그 HTML이 필요로 하는 CSS, 이미지, 브라우저용 JavaScript 파일도 함께 제공할 수 있습니다.
이번 장의 핵심 줄은 다음입니다.
app.use(express.static(path.join(__dirname, 'public')));이 한 줄로 public 폴더는 브라우저가 요청할 수 있는 정적 파일 공간이 됩니다.
그리고 특정 HTML 파일을 보내고 싶을 때는 다음처럼 씁니다.
res.sendFile(path.join(__dirname, 'views', 'home.html'));이제 Express 서버는 짧은 문장만 응답하는 단계를 넘어, 실제 웹페이지 파일을 브라우저에 전달할 수 있게 되었습니다. 다음 장에서는 사용자가 HTML 폼에 입력한 내용을 서버가 받아 처리하는 흐름으로 넘어갑니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.