
$ npm init
#콘솔창
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
press ^C at any time to quit.
package name: (폴더명) [프로젝트 이름 입력]
version: (1.0.0)[프로젝트 버전 입력]
description: [프로젝트 버전 입력]
entry point: index.js
test command : [엔터 키 클릭]
git repository: [엔터 키 클릭]
author: [사용자의 이름 입력]
license: (ISC)[엔터 키 클릭]
About to write to C:\Users\zerocho\npmtest\package.json:
{
"name": "npmtest",
"version": "0.0.1",
"description":"hello package.json",
"main":"index.js",
"scripts":{
"test":"echo \"Error: no test specified\" && exit1"
},
"author":"ZeroCho",
"license":"ISC"
}
Is this ok? (yes) yes
1) package name: 패키지 이름. package.json의 name의 속성에 저장
2) version: 패키지의 버전
3) entry point: 자바스크리브 실행 파일 진입점. 보통 마지막으로 module.exports를 하는 파일 지정
4) test command: 코드 테스트 할 때 입력할 명령어 의미. package.jsonscript 속성 안의 test속성에 저장됨
5) git repository: 코드를 저장해둔 깃 저장소
6) keywords: npm공식 홈페이지에서 패키지를 쉽게 찾을 수 있게 함
7) license: 해당 패키지의 라이선스를 넣으면 됌
npm install [패키지 이름]-> 익스프레스 설치 목적
#package.json 파일 (npm init 실행 후)
{
"name":"npmtest",
"version":"0.0.1",
"description":"hello package.json",
"main":"index.js",
"scripts":{
"test" : "echo\"Error: no test specified\" && exit 1"
},
"author":"ZeroCho",
"license":"ISC"
}
ex) 해당 코드에선 npm run test 수행하면 echo “Error: no test specified"란 문자열이 콘솔에 찍히고 exit1의 의미인 에러와 함께 종료.
#npm install 패키지 후의 package.json
{
"name":"npmtest",
...
"license":"ISC",
"dependencies" : {
"express" : "^4.17.3",
}
}
패키지 설치 후 package.json의 특징
1) dependencies 속성 생성됨- 그 안에 express라는 이름이랑 버전이 저장.
2) node_modules폴더 생성- 설치한 패키지들이 들어있음. express하나만 설치했는데 패키지가 여러개 들어있는 이유는 express가 의존하는 패키지들이 존재하기에
3) package-lock.json 파일 생성
4) peerDependencies
4. 패키지 버전
패키지 버전을 1.0.8이라고 가정!
- 1에 해당하는 첫번째 자리는 메이저 버전
- 0에 해당하는 두번째 자리는 마이너 버전. 하위 호환이 되는 기능 업데이트를 할 때 올림.
- 8에 해당하는 세번째 자리는 패치 버전. 기능 추가보단 기능에 문제가 있던 걸 수정한 것
익스프레스: npm에서 서버를 제작하는 과정에서 겪게 되는 불편을 해소하고 편의 기능을 추가한 웹 서버 프레임워크
1) learn-express 폴더 생성
2) package.json 제일 먼저 생성 (npm init 명령어를 콘솔에서 호출 후 단계적으로 내용물 입력)
3) scripts 부분에 start 속성 잊지 말고 삽입(nodemon app이면 app.js를 nodemon으로 실행하는 것)
//package.json
{
"name":"leaern-express",
"version":"0.0.1",
"description":"익스프레스를 배우자",
"main":"app.js",
"scripts":{
"start":"nodemon app"
},
"author":"Zerocho",
"license":"MIT"
}
4) app.js 작성
//app.js
const express = require('express');
const app= express();
app.set('port',process.env.PORT || 3000);
app.get('/', (req,res) =>{
res.send('Hello,Express');
});
app.listen(app.get('port'), ()=> {
console.log(app.get('port'),'번 포트에서 대기중');
});
// app.js -연결이 html인거
const express= require('express');
const path=require('path');
const app= express();
app.set('port',process.env.PORT||3000);
app.get('/',(req,res) =>{
res.sendFile(path.join(__dirname, '/index.html'));
});
app.listen(app.get('port'),() =>{
console.log(app.get('port'),'번 포트에서 대기중');
});
//app.js 예시
...
app.set('port',process.env.PORT||3000);
app.use((req,res,next) => {
console.log('모든 요청에 다 실행됩니다,');
next();
});
app.get('/',(req,res,next) =>{
console.log('GET / 요청에서만 실행됩니다.');
next();
}, (req,res) =>{
throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});
app.use((err,req,res,next) =>{
console.error(err);
res.status(500).send(err.message);
});
app.listen(app.get('port'),() =>{
...
에러 처리 미들웨어를 직접 연결하지 않아도 기본적으로 익스프레스가 처리를 하지만 실무에선 직접 연결해주는 게 좋음.
*실무에서 자주 사용하는 패키지 다운 명령어
npm i morgan cookie-parser express-session dotenv
const express = require('expess');
const morgan = require('morgan');
const cookieParser = require('cookieParser');
const session = require('session');
const dotenv = require('dotenv');
const path = require('path');
dotenv.config();
const app= express();
app.set('port',process.env.PORT||3000);
//설치한 패키지들을 불러와 app.use에 연결.
app.use(morgan('dev'));
app.use('/',express.static(path.join(__dirname,'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false}));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
resave: false,
saveUninitialized: false;
secret: process.env.COOKIE_SECRET,
cookie:{
httpOnly: true,
secure: false,
},
name: 'session-cookie',
})));
app.use(req,res,next) => {
console.log('모든 요청에 다 실행됩니다.');
}));
...
설치한 패키지들을 불러와 app.use에 연결.
dotenv패키지는 .env 파일을 읽어 process.env로 만듦
주의! 실제 서버의 폴더 경로에는 public이 들어 있지만, 요청 주소에는 public이 들어있지 않음. 서버의 폴더 경로와 요청 겨오가 달라 외부인이 서버의 구조를 쉽게 파악할 수 없음. (보안에 좋음)
주의: body-parser는 텍스트 기반의 본문만 처리함. 파일 업로드(multipart/form-data)는 처리하지 못하며, 이는 multer가 담당
app.use(cookieParser('나만의비밀키'));
주의: cookie-parser는 쿠키를 읽고 파싱하는 역할만 합니다. 쿠키를 생성(res.cookie)하거나 삭제(res.clearCookie)하는 것은 res 객체의 메서드를 사용해야 합니다.
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET, // cookieParser의 비밀 키와 동일하게 설정
cookie: {
httpOnly: true,
secure: false, // 배포 시에는 true로 설정
maxAge: 60 * 60 * 1000 // 1시간
},
name: 'connect.sid' // 세션 쿠키 이름 (기본값)
}));
const multer = require('multer');
const path = require('path');
const fs = require('fs');
// 'uploads' 폴더가 없으면 생성
try {
fs.readdirSync('uploads');
} catch (error) {
console.error('uploads 폴더가 없어 생성합니다.');
fs.mkdirSync('uploads');
}
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads/'); // 저장할 경로
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
const basename = path.basename(file.originalname, ext);
// 파일명 중복 방지를 위해 현재 시간을 추가
done(null, basename + Date.now() + ext);
}
}),
limits: { fileSize: 5 * 1024 * 1024 } // 5MB 파일 크기 제한
});
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="many" multiple />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
app.post('/upload', upload.array('many'), (req, res) => {
console.log(req.files); // 업로드된 파일 정보 배열
console.log(req.body); // 텍스트 필드 정보 (title)
res.send('ok');
});
<input type="file" name="image1">, <input type="file" name="image2">처럼 서로 다른 name 속성을 가진 여러 개의 파일 입력을 처리할 때 사용합니다.
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="image1" />
<input type="file" name="image2" />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
app.post('/upload',
upload.fields([{ name: 'image1' }, { name: 'image2' }]),
(req, res) => {
console.log(req.files.image1); // image1 파일 정보
console.log(req.files.image2); // image2 파일 정보
console.log(req.body); // 텍스트 필드 정보 (title)
res.send('ok');
}
);
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
app.post('/upload', upload.none(), (req, res) => {
console.log(req.body); // 텍스트 필드 정보 (title)
res.send('ok');
});
app.js 파일 하나에 app.get(), app.post() 등이 계속 늘어나면 코드가 길어지고 관리가 매우 어려워짐.
Express는 express.Router를 통해 라우팅 로직을 기능별로 별도의 파일로 분리할 수 있게 해줌.
먼저, routes 폴더를 만들고 그 안에 기능별 라우터 파일을 생성.
const express = require('express');
const router = express.Router();
// GET /
router.get('/', (req, res) => {
res.send('Hello, Express');
});
module.exports = router;
const express = require('express');
const router = express.Router();
// GET /user/
router.get('/', (req, res) => {
res.send('Hello, User');
});
module.exports = router;
app.js에서는 require로 라우터 파일을 불러와 app.use()로 연결.
이 때, 공통되는 주소를 app.use()의 첫 번째 인수로 지정 가능.
// app.js
const indexRouter = require('./routes'); // ./routes/index.js와 동일
const userRouter = require('./routes/user');
// ... (세션, 쿠키파서 미들웨어) ...
// indexRouter를 / 경로에 연결
app.use('/', indexRouter);
// userRouter를 /user 경로에 연결
app.use('/user', userRouter);
// ... (404, 에러 핸들러) ...
라우터를 더 효율적으로 관리하는 몇 가지 유용한 기법
/abc라는 동일한 주소에 대해 GET, POST, PUT 등 여러 메서드를 처리할 때, app.route()를 사용하면 코드를 깔끔하게 그룹화할 수 있습니다.
app.get('/abc', (req, res) => { res.send('GET /abc'); });
app.post('/abc', (req, res) => { res.send('POST /abc'); });
app.route('/abc')
.get((req, res) => { res.send('GET /abc'); })
.post((req, res) => { res.send('POST /abc'); });
주소의 특정 부분을 동적으로 받아 처리해야 할 때 사용. (예: 사용자 ID)
// :id 부분이 매개변수입니다.
router.get('/user/:id', (req, res) => {
// req.params 객체에 담깁니다.
console.log(req.params.id); // '123'
res.send('User ID: ' + req.params.id);
});
주의! 라우트 매개변수를 사용하는 라우터는 일반 라우터보다 항상 뒤에 배치해야 함.
// (O) 올바른 순서
router.get('/user/like', ...); // 1. /like 먼저 처리
router.get('/user/:id', ...); // 2. 그 외 /:id 처리
// (X) 잘못된 순서
router.get('/user/:id', ...); // '/user/like' 요청도 :id가 'like'라고 인식
router.get('/user/like', ...); // 이 코드는 절대 실행되지 않음
미들웨어와 라우터가 항상 다루는 req와 res 객체에는 Express가 추가한 유용한 속성과 메서드가 많음.
이러한 res 객체의 메서드들은 **메서드 체이닝(Method Chaining)**을 지원하는 경우가 많아 코드를 간결하게 작성 가능.
res.status(201)
.cookie('test', 'test')
.redirect('/admin');
미들웨어 스택의 마지막에는 404 처리와 에러 핸들러를 배치해야 함.
// ... (라우터 미들웨어들) ...
// 1. 404 처리 미들웨어
// 위쪽의 모든 라우터에서 요청을 처리하지 못한 경우 여기까지 내려옴
app.use((req, res, next) => {
res.status(404).send('Not Found');
});
// 2. 에러 처리 미들웨어
// (err, req, res, next) 매개변수 4개를 모두 가져야 함
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send('Internal Server Error');
});
먼저 npm으로 pug를 설치
$ npm i pug
그다음 app.js에서 Express가 Pug를 템플릿 엔진으로 사용하도록 설정.
// app.js
const path = require('path');
// ...
// 1. 템플릿 파일들이 위치할 폴더를 지정합니다. (기본값: views)
app.set('views', path.join(__dirname, 'views'));
// 2. 사용할 템플릿 엔진을 pug로 설정합니다.
app.set('view engine', 'pug');
// ...
이제 res.send() 대신 res.render() 메서드를 사용 가능.
router.get('/', (req, res, next) => {
// views 폴더의 index.pug 파일을 렌더링합니다.
// { title: 'Express' } 객체를 통해 변수를 전달합니다.
res.render('index', { title: 'Express' });
});
p
| 안녕하세요.
| 여러 줄을 입력합니다.
태그 뒤에 .을 붙여 CSS나 JavaScript 코드를 직접 작성 가능
style.
h1 { font-size: 30px; }
script.
const message = 'Pug';
alert(message);
h1= title
// 결과: <h1>Express</h1>
p!= '<strong>강조</strong>'
// 결과: <p><strong>강조</strong></p>
p Welcome to #{title}
// 결과: <p>Welcome to Express</p>
Tip: res.render 외에 res.locals 객체를 사용해 변수를 전달할 수도 있습니다. res.locals.title = 'Express'와 같이 설정하면, 해당 요청을 처리하는 모든 미들웨어와 템플릿에서 title 변수에 접근할 수 있습니다.
if, else, case를 사용하여 조건부 렌더링 가능
if isLoggedIn
div 로그인이 되었습니다.
else
div 로그인이 필요합니다.
case fruit
when 'apple'
p 사과입니다.
when 'banana'
p 바나나입니다.
default
p 사과도 바나나도 아닙니다.
each를 사용해 배열을 순회합니다. for를 써도 동일하게 작동
ul
each fruit in ['사과', '배', '오렌지']
li= fruit
// 인덱스 사용 시
ul
each fruit, index in ['사과', '배', '오렌지']
li= (index + 1) + '번째 ' + fruit
웹사이트는 보통 헤더, 푸터, 내비게이션 등 공통된 레이아웃을 가집니다. Pug는 이를 효율적으로 관리하는 두 가지 방법을 제공
header
a(href='/') Home
main
include header // header.pug 파일 내용이 여기에 삽입됨
h1 메인 파일
include footer
더 강력한 레이아웃 관리 방식. 공통의 "틀"(layout.pug)을 만들고, 페이지마다 달라지는 부분(block)만 채워 넣음.
doctype html
html
head
title= title
link(rel='stylesheet', href='/style.css')
body
// 이 부분이 페이지마다 다른 내용으로 채워짐
block content
// layout.pug 파일을 상속받음
extends layout
// layout.pug의 'block content' 부분을 이 내용으로 대체
block content
main
p 내용입니다.
res.render('body')를 호출하면, Pug는 body.pug가 상속한 layout.pug를 먼저 읽고, 그 안의 block content를 body.pug의 block content 내용으로 채워 넣어 최종 HTML을 완성
먼저 npm으로 nunjucks를 설치
$ npm i nunjucks
app.js 설정은 Pug와 조금 다름. Nunjucks는 .html 확장자를 그대로 사용하는 경우가 많음
// app.js
const nunjucks = require('nunjucks');
// ...
// 1. 사용할 템플릿 엔진을 'html'로 설정합니다.
// (Pug와 달리, 확장자를 njk 등으로 커스텀할 수도 있습니다)
app.set('view engine', 'html');
// 2. Nunjucks를 설정합니다.
nunjucks.configure('views', { // 템플릿 파일들이 위치할 폴더
express: app, // express 객체 연결
watch: true, // true로 설정하면 HTML 파일 변경 시 템플릿 엔진이 다시 렌더링
});
// ...
Nunjucks는 변수와 로직을 위한 특별한 기호 {{ }}와 {% %}를 사용.
Express에서 res.render('index', { title: 'Express' })로 전달한 변수는 {{ }}를 사용해 출력.
<h1>{{ title }}</h1>
<p>{{ '<strong>strong</strong>' | safe }}</p>
{% set js = 'Javascript' %}
<p>{{ js }}</p>
{% for %}와 {% endfor %} 블록을 사용하여 배열을 순회.
{% set fruits = ['사과', '배', '오렌지', '복숭아'] %}
<ul>
{% for item in fruits %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<ul>
{% for item in fruits %}
<li>{{ loop.index + 1 }}번째 {{ item }}</li>
{% endfor %}
</ul>
{% if %}, {% elif %}, {% else %}, {% endif %}를 사용합니다.
{% if isLoggedIn %}
<div>로그인이 되었습니다.</div>
{% else %}
<div>로그인이 필요합니다.</div>
{% endif %}
{% if fruit == 'apple' %}
<p>사과입니다.</p>
{% elif fruit == 'banana' %}
<p>바나나입니다.</p>
{% else %}
<p>사과도 바나나도 아닙니다.</p>
{% endif %}
Nunjucks도 include, extends, block을 통해 효율적으로 레이아웃을 관리
Pug와 동일하게 헤더, 푸터 등 공통 요소를 삽입할 때 사용
<main>
{% include "header.html" %}
<h1>메인 파일</h1>
{% include "footer.html" %}
</main>
Pug와 정확히 같은 개념으로, HTML 문법으로 작성된다는 점만 다름
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
{% extends 'layout.html' %}
{% block content %}
<h1>{{ title }}</h1>
<p>Welcome to {{ title }}</p>
{% endblock %}
404나 500 에러가 발생했을 때, Nunjucks로 만든 에러 페이지를 보여줄 수 있음
// app.js
// 404 처리 미들웨어
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error); // 에러 핸들러로 넘김
});
// 에러 처리 미들웨어 (맨 마지막에 위치)
app.use((err, req, res, next) => {
// 템플릿에 전달할 변수 설정
res.locals.message = err.message;
// 개발 환경에서만 스택 트레이스 노출
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
res.status(err.status || 500);
res.render('error'); // views/error.html을 렌더링
});
{% extends 'layout.html' %}
{% block content %}
<h1>{{ message }}</h1>
<h2>{{ error.status }}</h2>
<pre>{{ error.stack }}</pre>
{% endblock %}
이렇게 설정하면 개발 중에는 상세한 에러 스택을 확인하고, 운영 환경에서는 사용자에게 보안에 민감한 스택 트레이스를 노출하지 않고 에러 메시지만 보여줄 수 있음
const session = require('express-session');
app.use(
session({
resave: false,
saveUninitialized: false,
// ⬇️ 빈칸: 환경 변수에서 비밀 키를 가져와 설정하세요.
secret: _________,
cookie: {
httpOnly: true,
secure: false,
},
})
);
2. 라우터에서 전달받은 fruits 배열을 Nunjucks 템플릿에서 for 문으로 순회하고 있습니다. li 태그 안에서 현재 순회의 아이템(항목)인 item 변수의 값을 출력하도록 빈칸을 채워주세요.
<ul>
{% for item in fruits %}
<li>{{ _________ }}</li>
{% endfor %}
</ul>
<빈칸QUIZ 답>
<코드작성QUIZ 답>
1. process.env.COOKIE_SECRET,
2. item
출처 : 조현영, 『 Node.js 교과서 개정 3판』, 길벗(2022)
node.js : https://nodejs.org/ko/learn
Corner Node.js 2
Editor Yeonyeon
| [Node.js 2팀] 8장. 몽고디비 (0) | 2025.11.21 |
|---|---|
| [Node.js 2팀] 7장. MySQL (1) | 2025.11.14 |
| [Node.js 2팀] 3장. 노드 기능 알아보기 ~ 4장. http 모듈로 서버 만들기 (0) | 2025.10.31 |
| [Node.js 2팀] 1장. 노드 시작하기 ~ 2장. 알아둬야 할 JavaScript (0) | 2025.10.10 |
| [Node.js 2팀] 1장 자바스크립트( Introductions ~ Objects ) (0) | 2025.10.03 |