
package.json은 설치한 패키지의 버전을 관리하는 파일로, 노드 프로젝트를 시작하기 전에는 폴더 내부에 무조건 package.json부터 만들고 시작해야 한다. npm은 package.json을 만드는 명령어를 제공한다.
구성
| package name | 패키지의 이름 |
| version | 패키지의 버전 |
| entry point | 자바스크립트 실행 파일 진입점, 보통 module.exports를 하는 파일을 저장 |
| test command | 코드를 테스트할 때 입력할 명령어 |
| git repository | 코드를 저장해둔 깃(Git) 저장소 주소 |
| keywords | npm 공식 홈페이지에서 패키지를 쉽게 찾을 수 있게 함 |
| license | 해당 패키지의 라이선스 |
프로젝트 이름과 설치하는 패키지 이름은 달라야 한다. 예를 들어, express라는 패키지를 설치했을 때 프로젝트 이름(package.json의 name)은 express여서는 안 된다. package.json 말고도 package-lock.json도 존재하는데, package-lock.json은 패키지 간의 의존관계를 명시한 파일이다.
모듈 여러 개를 동시에 설치할 때는 npm install [패키지1] [패키지2] […] 명령어를 사용하고, 개발용 패키지를 설치할 때는 npm install --save-dev [패키지] […] 명령어를 사용한다. 이때 --save-dev가 개발용 패키지임을 나타낸다. package.json 파일에서는 devDependencies 속성에서 개발용 패키지들만 따로 관리한다.
peerDependencies와 다른 버전이 설치되어 있다면 ERESOLVE unable to resolve dependency tree 에러 메시지가 표시된다. 만약 동시에 사용하는 여러 패키지의 peerDependencies가 다른 경우 npm i --force를 통해 강제로 모든 버전을 설치하거나npm i --legacy-peer-deps로 peerDependencies를 무시하는 방법을 사용한다.
npm에는 전역(global) 설치 옵션도 존재하는데, 이는 패키지를 npm이 설치되어 있는 폴더에 설치하는 방식이다. 전역 설치한 패키지는 콘솔의 명령어로 사용 가능하다.
노드 패키지들의 버전은 항상 세 자리로 이루어져 있는데, 이는 SemVer 방식의 버전 넘버링을 따르기 때문이다. SemVer은 Semantic Versioning(유의적 버전)의 줄임말로, 버전을 구성하는 세 자리가 모두 의미를 갖고 있다는 뜻을 가진다.
package.json에는 세 버전 외에도 버전 앞에 ^이나 ~ 또는 >, < 같은 문자가 붙어 있다. 각 문자들은 설치하거나 업데이트할 때 어떤 버전을 설치해야 하는지 알리는 역할을 한다.
Current와 Wanted가 다르다면 업데이트가 필요한 경우이기 때문에 npm update [패키지 이름]으로 업데이트한다. npm update를 하면 업데이트 가능한 모든 패키지가 Wanted에 적힌 버전으로 업데이트된다.
| npm uninstall [패키지 이름] or npm rm [패키지 이름] | 해당 패키지를 제거, node_modules 폴더와 package.json에서 사라짐 |
| npm search [검색어] | npm의 패키지 검색 |
| npm info [패키지 이름] | 패키지의 세부 정보를 파악하고자 할 때 사용 |
| npm login | npm 로그인을 위한 명령어 |
| npm whoami | 로그인한 사용자가 누구인지 알림, 로그인된 상태가 아니라면 에러 발생 |
| npm logout | npm login으로 로그인한 계정을 로그아웃 |
| npm version [버전] | package.json의 버전을 올림, 원하는 버전의 숫자를 넣거나 major, minor, patch라는 문자열을 넣어서 해당 부분의 숫자를 1 올릴 수도 있음 |
| npm deprecate [패키지 이름] [버전] [메시지] | 해당 패키지를 설치할 때 경고 메시지를 띄우게 하는 명령어, 자신의 패키지에만 이 명령어를 적용할 수 있음, 다른 사용자들이 버그가 있는 버전의 패키지를 설치할 때 경고 메시지가 출력됨 |
| npm publish | 자신이 만든 패키지를 배포할 때 사용 |
| npm unpublish | 배포한 패키지를 제거, 24시간 이내에 배포한 패키지만 제거 가능(의존성 관계 때문) |
npm은 패키지의 이름이 겹치는 것을 허용하지 않는다. 만약 남이 사용하는 패키지 이름으로 배포하고 싶다면 네임스페이스를 사용할 수 있다.
누군가가 이름을 사용하고 있는지 확인하려면 npm info [패키지 이름]을 콘솔에 입력하여 확인한다. 정보가 나온다면 누군가가 사용하고 있는 이름이고, npm ERROR! code E404 에러가 발생한다면 사용해도 좋은 이름이 된다. 패키지를 삭제할 때는 npm unpublish [패키지 이름] --force 명령어를 사용한다. 삭제 후 npm info 명령어를 사용해 삭제된 것을 확인한다.
npm에는 서버를 제작하는 과정에서 겪게 되는 불편을 해소하고 편의 기능을 추가한 웹 서버 프레임워크가 존재하는데,
대표적인 것이 익스프레스이다.
항상 package.json을 제일 먼저 생성한다. npm init 명령어를 콘솔에서 호출해 단계적으로 내용물을 입력해도 되고, npm init -y를 입력해 파일을 만든 뒤 내용을 수정할 수도 있다.
{
"name": "learn-express",
"version": "0.0.1",
"description": "익스프레스를 배우자",
"main": "app.js",
"scripts": {
"start": "nodemon app"
},
"author": "ZeroCho",
"license": "MIT"
}
scripts 부분에 start 속성은 잊지 말고 넣어줘야 한다. "start" : "nodemon app"은 app.js를 nodemon으로 실행한다는 뜻이다. 서버 코드에 수정 사항이 생길 때마다 매번 서버를 재시작하기 귀찮기 때문에 서버를 자동으로 재시작한다.
nodemon은 개발용으로만 사용할 것을 권장한다. 배포 후에는 서버 코드가 빈번하게 변경될 일이 없으므로 nodemon을 사용하지 않아도 된다.
서버의 역할을 할 app.js를 작성한다.
const express = require('express');
const app = express();
app.set('port', process.env.PORT || 3000); // 서버가 실행될 포트 설정
// process.env 객체에 PORT 속성이 있다면 그 값을 사용, 없다면 기본값으로 3000번 포트를 이용하도록 설정
app.get('/', (req, res) => { // app.get(주소, 라우터) : 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 적음
res.send('Hello, Express'); // 익스프레스에서는 res.write나 res.end 대신 red.send를 사용
// req : 요청에 관한 정보가 들어 있는 객체
// res : 응답에 관한 정보가 들어 있는 객체
});
app.listen(app.get('port'), () => { // 웹 서버와 동일
console.log(app.get('port'), '번 포트에서 대기 중');
});
Express 모듈을 실행해 app 변수에 할당하며, 익스프레스 내부에 http 모듈이 내장되어 있으므로 서버의 역할을 할 수 있다.
GET 요청 외에도 POST, PUT, PATCH, DELETE, OPTIONS에 대한 라우터를 위한 메서드가 존재한다. 또한, 단순한 문자열 대신 HTML로 응답하고 싶다면 res.sendFile 메서드를 사용한다. 단, 파일의 경로를 path 모듈을 사용해서 지정해야 한다.
미들웨어는 익스프레스의 핵심으로, 요청과 응답의 중간(middle)에 위치하기 때문에 미들웨어라고 부른다. 라우터와 에러 핸들러 또한 미들웨어의 일종이며, 미들웨어는 요청과 응답을 조작해 기능을 추가하기도 하고, 나쁜 요청을 걸러내기도 한다.
미들웨어는 app.use와 함께 app.use(미들웨어) 형태로 사용된다.
...
app.set('port', process.env.PORT || 3000);
app.use((req,res, next) => { // app.use에 매개변수가 req, res, next인 함수를 넣음
console.log('모든 요청에 다 실행됩니다.');
next(); // next : 다음 미들웨어로 넘어가는 함수(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'), () => {
...
미들웨어는 위에서부터 아래로 순서대로 실행되면서 요청과 응답 사이에 특별한 기능을 추가할 수 있다. 주소를 첫 번째 인수로 넣어주지 않으면 미들웨어는 모든 요청에서 실행되고, 주소를 넣으면 해당하는 요청에서만 실행된다.
| app.use(미들웨어) | 모든 요청에서 미들웨어 실행 |
| app.use('/abc', 미들웨어) | abc로 시작하는 요청에서 미들웨어 실행 |
| app.post('/abc', 미들웨어) | abc로 시작하는 POST 요청에서 미들웨어 실행 |
app.use나 app.get 같은 라우터에 미들웨어를 여러 개 장착할 수 있다. 위의 코드에서는 app.get 미들웨어가 두 개 연결되어 있는데, 이때도 next를 호출해야 다음 미들웨어로 넘어갈 수 있다.
또한, 에러 처리 미들웨어는 모두 사용하지 않더라도 매개변수가 반드시 4개여야 한다. 위의 코드 첫 번째 매개변수 err에는 에러가 관한 정보가 담겨 있다. res.status 메서드로는 HTTP 상태 코드를 지정할 수 있는데, 기본값은 200으로 성공을 나타낸다. 에러 처리 미들웨어는 특별한 경우가 아니면 가장 아래에 위치하도록 한다.
미들웨어는 req, res, next를 매개변수로 갖는 함수로서 app.use나 app.get, app.post 등으로 장착한다. 동시에 여러 개의 미들웨어를 장착할 수도 있으며, 다음 미들웨어로 넘어가려면 next 함수를 호출해야 한다. next를 호출하지 않는 미들웨어는 res.send나 res.sendFile 등의 메서드로 응답을 보내야 한다.
미들웨어를 사용할 때 유용한 패턴으로 미들웨어 안에 미들웨어를 넣는 방식이 존재한다. 이 방식은 기존 미들웨어의 기능을 확장할 수 있기 때문에 유용하다. 예를 들어, 분기 처리를 할 수 있다.
app.use(morgan('dev'));
// 또는
app.use((req, res, next) => {
morgan('dev')(req, res, next);
});
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="image" />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
폼을 통해 업로드하는 파일은 body-parser로는 처리할 수 없고 직접 파싱(해석)하기도 어려우므로 multer라는 미들웨어를 따로 사용하면 편리하다.
const multer = require('multer');
const upload = multer({ // multer 함수의 인수로 설정을 넣음
storage: multer.disStorage({ // storage 속성에는 어디에(destination) 어떤 이름으로(filename) 저장할지 넣음
destination(req, file, done) { // file 객체에는 업로드한 파일에 대한 정보가 들어감
done(null, 'uploads/');
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
done(null, path.basename(file.originalname, ext) + Date.now() + ext);
},
}),
limits: {fileSize: 5 * 1024 * 1024}, // limits 속성에는 업로드에 대한 제한 사항을 설정할 수 있음
});
done 매개변수는 함수이다. 첫 번째 인수에는 에러가 있다면 에러를 넣고, 두 번째 인수에는 실제 경로나 파일 이름을 넣어준다. req나 file의 데이터를 가공해서 done으로 넘기는 형식이다.
위 설정을 실제로 활용하려면 서버에 uploads 폴더가 꼭 존재해야 하는데, 없다면 직접 만들어주거나 fs 모듈을 사용해서 서버를 시작할 때 생성한다.
파일을 하나만 업로드하는 경우 single 미들웨어를 사용한다. single 미들웨어를 라우터 미들웨어 앞에 넣어두면 multer 설정에 따라 파일 업로드 후 req.file 객체가 생성된다. 인수는 input 태그의 name이나 폼 데이터의 키와 일치하게 넣는다. 업로드 성공 시 결과는 req.file 객체 안에 들어있으며 req.body에는 파일이 아닌 데이터인 title이 들어 있다.
app.post('/upload', upload.single('image'),
(req, res) => { console.log(req.file, req.body);
res.send('ok'); });
여러 파일을 업로드하는 경우 HTML의 input 태그에는 multiple을 사용한다. 미들웨어는 single 대신 array로 교체하며, 업로드 결과도 req.file 대신 req.files 배열에 들어 있다.
app.post('/upload', upload.array('many'), (req, res) => {
console.log(req.files, req.body);
res.send('ok');
});
파일을 여러 개 업로드하지만 input 태그나 폼 데이터의 키가 다른 경우 fields 미들웨어를 사용한다.
파일을 업로드하지 않고도 멀티파트 형식으로 업로드하는 경우 none 미들웨어를 사용한다. 이때, 파일을 업로드하지 않았으므로 req.body만 존재한다.
// index.js
const express = require('express');
const router = express.Router();
// GET / 라우터
router.get('/', (req, res) => {
res.send('Hello, Express');
});
module.exports = router;
// user.js
const express = require('express');
const router = express.Router();
// GET /user 라우터
router.get('/', (req, res) => {
res.send('Hello, User');
});
module.exports = router;
만들어진 index.js, user.js를 app.use를 통해 app.js에 연결하고 에러 처리 미들웨어 위에 404 상태 코드를 응답하는 미들웨어를 하나 추가한다.
...
const path = require('path');
dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
...
name: 'session-cookie',
})));
app.use('/', indexRouter);
app.use('/user', userRouter);
app.use((req, res, next) => {
res.status(404).send('Not Found');
});
app.use((err, req, res, next) => {
...
index. js와 user.js는 모양이 거의 비슷하지만 다른 주소의 라우터 역할을 하고 있는데, app.use로 연결할 때의 차이 때문이다. indexRouter는 app.use(’/’)에 연결하고, userRouter는 app.use(’/User’)에 연결한다. 따라서 indexRouter는 use의 ‘/’와 get의 ‘/’가 합쳐져 GET / 라우터가, userRouter는 use의 ‘/user’와 get의 ‘/’가 합쳐져 GET /user 라우터가 된다.
next 함수에는 다음 라우터로 넘어가는 기능이 존재하는데, next(’route’) 형태로 사용한다. 이 기능은 라우터에 연결된 나머지 미들웨어들을 건너뛰고 싶을 때 사용한다.
라우트 매개변수
router.get('/user/:id', (req, res) => { // :id에는 다른 값을 넣을 수 있음(/users/1 등)
console.log(req.params, req.query);
});
// 라우트 매개변수 사용 시 주의점 : 일반 라우터보다 뒤에 위치해야 함
// 다양한 라우터를 아우르는 역할을 하므로 뒤에 위치해야 다른 라우터를 방해하지 않음
주소에 쿼리스트링을 쓸 때도 있는데, 이때 쿼리스트링의 키-값 정보는 req.query 객체 안에 존재한다. 예를 들어, /users/123?limit=5&skip=10이라는 주소의 요청이 들어왔을 때 req.params와 req.query 객체는 각각 {id: '123'} {limit: ‘5’, skip: '10'}이다.
또한, 웬만하면 404 응답 미들웨어와 에러 처리 미들웨어를 연결해주는 것이 좋다.
익스프레스의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것이다. 따라서 기존 http 모듈의 메서드도 사용할 수 있고, 익스프레스가 추가한 메서드나 속성을 사용할 수 있다. 예를 들어, res.writeHead, res.write, res.end 메서드를 그대로 사용할 수 있으면서 res.send나 res.sendFile 같은 메서드도 쓸 수 있는 것이다. 그러나 익스프레스의 메서드가 워낙 편리하므로 기존 http 모듈의 메서드는 잘 쓰이지 않는다.
자주 쓰이는 속성과 메서드(req 객체)
| req.app | req 객체를 통해 app 객체에 접근 가능 |
| req.body | body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체 |
| req.cookies | cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체 |
| req.ip | 요청의 ip 주소 |
| req.params | 라우트 매개변수에 대한 정보가 담긴 객체 |
| req.query | 쿼리스트링에 대한 정보가 담긴 객체 |
| req.signedCookies | 서명된 쿠키들이 담겨 있음 |
| req.get(헤더 이름) | 헤더의 값을 가져오고 싶을 때 사용하는 메서드 |
자주 쓰이는 속성과 메서드(res 객체)
| res.app | req.app처럼 res 객체를 통해 app 객체에 접근할 수 있음 |
| res.cookie(키, 값, 옵션) | 쿠키를 설정하는 메서드 |
| res.clearCookie(키, 값, 옵션) | 쿠키를 제거하는 메서드 |
| res.end() | 데이터 없이 응답을 보냄 |
| res.json(JSON) | JSON 형식의 응답을 보냄 |
| res.locals | 하나의 요청 안에서 미들웨어 간에 데이터를 전달하고 싶을 때 사용하는 객체 |
| res.redirect(주소) | 리다이렉트할 주소와 함께 응답을 보냄 |
| res.render(뷰, 데이터) | 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드 |
| res.send(데이터) | 데이터와 함께 응답을 보냄 |
| res.sendFile(경로) | 경로에 위치한 파일을 응답 |
| res.set(헤더, 값) | 응답의 헤더를 설정 |
| res.status(코드) | 응답 시의 HTTP 상태 코드를 지정 |
각 메서드는 메서드 체이닝(method chaining)을 지원하는 경우가 많은데, 메서드 체이닝을 활용하면 코드 양을 줄일 수 있다.
템플릿 엔진은 자바스크립트를 사용해서 HTML을 렌더링할 수 있게 한다. 따라서 기존 HTML과는 문법이 살짝 다를 수도 있고, 자바스크립트 문법이 들어 있기도 하다.
6.5.1 퍼그(제이드)
퍼그는 문법이 간단해서 코드양이 줄어들기 때문에 꾸준한 인기를 얻고 있다. 퍼그 설치는 npm i pug 명령어를 사용하여 수행하고, 퍼그를 익스프레스와 연결하려면 app.js에 다음 부분이 들어있어야 한다.
...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
// views : 템플릿 파일들이 위치한 폴더들을 지정, res.render 메서드가 이 폴더 기준으로 템플릿 엔진을 찾아서 렌더링
app.set('view engine', 'pug');
// view engine : 어떠한 종류의 템플릿 엔진을 사용할지를 나타냄
app.use(morgan('dev'));
...
6.5.1.1 HTML 표현
6.5.1.2 변수
6.5.1.3 반복문
6.5.1.4 조건문
6.5.1.5 include
6.5.1.6 extends와 block
6.5.2 넌적스
넌적스는 퍼그의 HTML 문법 변화에 적응하기 힘든 사람들에게 유용한 템플릿 엔진이다. 파이어폭스를 개발한 모질라에서 만들었으며, HTML 문법을 그대로 사용하되 추가로 자바스크립트 문법을 사용할 수 있다. 파이썬의 템플릿 엔진인 Twig와 문법이 상당히 유사한 넌적스는 npm i nunjucks 명령어를 사용하여 설치한다.
6.5.2.1 변수
6.5.2.2 반복문
6.5.2.3 조건문
6.5.2.4 include
6.5.2.5 extends와 block
6.5.3 에러 처리 미들웨어
에러 처리 미들웨어는 error라는 템플릿 파일을 렌더링하는데, 렌더링 시 res.locals.message와 res.locals.error에 넣어준 값을 함께 렌더링한다. 또한, res.render에 변수를 대입하는 것 외에도 res.locals 속성에 값을 대입해 템플릿 엔진에 변수를 주입할 수 있다.
error 객체의 스택 트레이스는 시스템 환경이 production(배포 환경)이 아닌 경우에만 표시된다. 배포 환경인 경우에는 에러 메시지만 표시되는데, 이는 에러 스택 트레이스가 노출되면 보안에 취약할 수 있기 때문이다.
1. 설치한 패키지의 버전을 관리하는 파일을 ( )라고 한다.
2. 미들웨어에서 다음 미들웨어로 넘어가려면 ( ) 함수를 호출해야 한다.
3. 요청과 응답에 대한 정보를 콘솔에 기록하는 미들웨어는?
4. 서명된 쿠키는 ( ) 객체에 저장되어 있다.
5. ( )는 퍼그의 HTML 문법 변화에 적응하기 힘든 사람들에게 유용한 템플릿 엔진이다.
6. 다음 HTML 코드를 퍼그 코드로 변환하세요.
<div id="card" class="product-card">
<h2>Corner</h2>
<p>코코넛들 화이팅</p>
<a href="https://example.com" target="_blank">자세히 보기</a>
</div>
7. body-parser 미들웨어를 사용하여 JSON 형식으로 데이터를 전달하고자 할 때, 사용하는 명령어를 작성하세요.
1. package.json
2. next
3. morgan
4. req.signedCookies
5. 넌적스
// 6번 문제 정답
#card.product-card
h2 Corner
p 코코넛들 화이팅
a(href='https://example.com', target='_blank') 자세히 보기
// 7번 문제 정답
app.use(express.json());
출처 : 조현영, 『 Node.js 교과서 개정 3판』, 길벗(2022),
Corner Node.js 1
Editor : Larva
| [Node.js 1팀] 8장. 몽고디비 (0) | 2025.11.21 |
|---|---|
| [Node.js 1팀] 7장. MySQL (0) | 2025.11.14 |
| [Node.js 1팀] 3장. 노드 기능, 4장. http 모듈로 서버 만들기 (0) | 2025.10.31 |
| [Node.js 1팀] 2장. 알아둬야 할 JavaScript (0) | 2025.10.10 |
| [Node.js 1팀] 1장. JavaScript (Introductions ~ Objects) (1) | 2025.10.03 |