상세 컨텐츠

본문 제목

[Node.js 1팀] 5장. 패키지 매니저, 6장. 익스프레스 웹 서버 만들기

25-26/Node.js 1

by a-rom 2025. 11. 7. 10:00

본문

728x90


 

5장. 패키지 매니저

5.1 npm 알아보기

  • npm : Node Package Manager
  • 대부분의 자바스크립트 프로그램은 패키지라는 이름으로 npm에 등록되어 있다.
  • npm에는 2022년 4월 기준 193만 개에 달하는 패키지가 등록되어 있다.
  • 패키지 : npm에 업로드 된 노드 모듈
    • 모듈이 다른 모듈을 사용할 수 있는 것처럼, 패키지가 다른 패키지를 사용할 수도 있는데, 이러한 관계를 의존관계라고 한다.

5.2 package.json으로 패키지 관리하기

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이 설치되어 있는 폴더에 설치하는 방식이다. 전역 설치한 패키지는 콘솔의 명령어로 사용 가능하다.

 

 

5.3 패키지 버전 이해하기

노드 패키지들의 버전은 항상 세 자리로 이루어져 있는데, 이는 SemVer 방식의 버전 넘버링을 따르기 때문이다. SemVer은 Semantic Versioning(유의적 버전)의 줄임말로, 버전을 구성하는 세 자리가 모두 의미를 갖고 있다는 뜻을 가진다.

  • 첫 번째 자리 : 메이저(major) 버전으로, 0이면 초기 개발 중 / 1부터는 정식 버전임을 나타낸다. 하위 호환이 안 될 정도로 패키지의 내용이 수정되었을 때 올린다. (ex. 1.5.0 → 2.0.0)
  • 두 번째 자리 : 마이너(minor) 버전으로, 하위 호환이 되는 기능 업데이트를 할 때 올린다.
  • 세 번째 자리 : 패치(patch) 버전으로, 기존 기능에 문제가 있어 수정한 것을 내놓았을 때 올린다.

package.json에는 세 버전 외에도 버전 앞에 ^이나 ~ 또는 >, < 같은 문자가 붙어 있다. 각 문자들은 설치하거나 업데이트할 때 어떤 버전을 설치해야 하는지 알리는 역할을 한다.

  • ^ : 마이너 버전까지만 설치하거나 업데이트한다. (ex. express@^ 1.1.1 : 1.1.1 이상부터 2.0.0 미만 버전까지 설치)
  • ~ : 패치 버전까지만 설치하거나 업데이트한다.
  • >, <, >=, <=, = : 초과, 미만, 이상, 이하, 동일을 의미한다.
  • @latest : 안정된 최신 버전의 패키지 설치로, x로도 표현한다.
  • @next : 가장 최근 배포판 설치를 나타내며, 안정되지 않은 알파나 베타 버전의 패키지를 설치할 수 있다.

 

5.4 기타 npm 명령어

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시간 이내에 배포한 패키지만 제거 가능(의존성 관계 때문)

 

 

 

5.5 패키지 배포하기

npm은 패키지의 이름이 겹치는 것을 허용하지 않는다. 만약 남이 사용하는 패키지 이름으로 배포하고 싶다면 네임스페이스 사용할 수 있다.

누군가가 이름을 사용하고 있는지 확인하려면 npm info [패키지 이름]을 콘솔에 입력하여 확인한다. 정보가 나온다면 누군가가 사용하고 있는 이름이고, npm ERROR! code E404 에러가 발생한다면 사용해도 좋은 이름이 된다. 패키지를 삭제할 때는 npm unpublish [패키지 이름] --force 명령어를 사용한다. 삭제 후 npm info 명령어를 사용해 삭제된 것을 확인한다.

 

 


6장. 익스프레스 웹 서버 만들기

npm에는 서버를 제작하는 과정에서 겪게 되는 불편을 해소하고 편의 기능을 추가한 웹 서버 프레임워크가 존재하는데,

대표적인 것이 익스프레스이다.

  • http 모듈의 요청과 응답 객체에 추가 기능들을 부여한다.
  • 편리한 메서드들을 추가해 기능을 보완한다.
  • 코드를 분리하기 쉽게 만들어 관리하기 용이하다.
  • if문으로 요청 메서드와 주소를 구별하지 않아도 된다.

 

6.1 익스프레스 프로젝트 시작하기

항상 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 모듈을 사용해서 지정해야 한다.

 

 

6.2 자주 사용하는 미들웨어

미들웨어는 익스프레스의 핵심으로, 요청과 응답의 중간(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으로 성공을 나타낸다. 에러 처리 미들웨어는 특별한 경우가 아니면 가장 아래에 위치하도록 한다.

 

6.2.1 morgan

  • 요청과 응답에 대한 정보를 콘솔에 기록한다.
  • app.use(morgan('dev'));와 같이 사용한다. 인수로 dev 외에 combined, common, short, tiny 등을 넣을 수 있다.

 

6.2.2 static

  • 정적인 파일들을 제공하는 라우터 역할을 한다.
  • 기본적으로 제공되기에 따로 설치할 필요 없이 express 객체 안에서 꺼내 장착하면 된다.
  • app.use('요청 경로', express.static('실제 경로')); 와 같이 사용하며, 함수의 인수로 정적 파일들이 담겨 있는 폴더를 지정한다.
    • ex. public/stylesheets/style.css는 http://localjost:3000/stylesheets/style.css로 접근할 수 있다.
    • 실제 서버의 폴더 경로에는 public이 들어 있지만 요청 주소에는 public이 들어 있지 않은데, 서버의 폴더 경로와 요청 경로가 다르므로 외부인이 서버의 구조를 쉽게 파악할 수 없어 보안에 큰 도움이 된다.
  • 정적 파일들을 알아서 제공해주므로 fs.readFile로 파일을 직접 읽어서 전송할 필요가 없다.
  • 만약 요청 경로에 해당하는 파일이 없으면 알아서 내부적으로 next를 호출한다.

 

6.2.3 body-parser

  • 요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어다.
  • 보통 폼 데이터나 AJAX 요청의 데이터를 처리한다.
  • 멀티파트(이미지, 동영상, 파일) 데이터는 처리하지 못하는데, 이 경우에는 multer 모듈을 사용한다.
  • app.use(express.join()); 또는 app.use(express.urlencoded({extended: false}));과 같이 사용한다.
  • 익스프레스 4.17.0 버전부터 body-parser 미들웨어의 기능이 익스프레스에 내장되었으므로 따로 설치할 필요 없다.
  • 익스프레스는 JSONURL-encoded 형식의 데이터 외에도 Raw, Text 형식의 데이터를 추가로 해석할 수 있다.
    • Raw : 요청의 본문이 버퍼 데이터일 때 해석하는 미들웨어이다.
    • Text : 요청의 본문이 텍스트 데이터일 때 해석하는 미들웨어이다.
    • JSON : JSON 형식의 데이터 전달 방식이다.
    • URL-encoded : 주소 형식으로 데이터를 보내는 방식으로, 폼 전송할 때 주로 사용한다. urlencoded의 옵션이 false이면 노드의 querystring 모듈을 사용해 쿼리스트링을 해석하고 true이면 qs 모듈을 사용해 쿼리스트링을 해석한다. 이때 qs 모듈은 내장 모듈이 아니라 npm 패키지이며, querystring 모듈의 기능을 좀 더 확장한 모듈이다.
  • POST와 PUT 요청의 본문을 전달받고자 할 때 패키지가 내부적으로 스트림을 처리해 req.body에 추가한다.

 

6.2.4 cookie-parser

  • 요청에 동봉된 쿠키를 해석해 req.cookies 객체로 생성한다.
  • app.use(cookieParser(비밀 키)); 와 같이 사용한다.
    • 첫 번째 인수로 비밀 키를 넣어줄 수 있다.
    • 서명된 쿠키가 있는 경우, 제공한 비밀 키를 통해 해당 쿠키가 내 서버가 만든 쿠키임을 검증한다.
    • 쿠키는 클라이언트에서 위조하기 쉬우므로 비밀 키를 통해 만들어낸 서명을 쿠키 값 뒤에 붙인다.
    • 서명이 붙으면 쿠키가 name=serocho.sjgn과 같은 모양이 되고, 서명된 쿠키는 req.signedCookies 객체에 들어 있다.
  • 해석된 쿠키들은 req.cookies 객체에 들어간다.
  • 유효 기간이 지난 쿠키는 알아서 걸러낸다.
  • 쿠키를 생성/제거하려면 res.cookie, res.clearCookie 메서드를 사용해야 한다.
  • 쿠키를 지우려면 키와 값 외에 옵션도 정확히 일치해야 쿠키가 지워진다. 이때, expries, maxAge 옵션은 제외한다.
  • signed 옵션을 true로 설정하면 쿠키 뒤에 서명이 붙는다. 내 서버가 쿠키를 만들었다는 것을 검증할 수 있으므로 대부분의 경우 서명 옵션을 켜두는 것이 좋다.

 

6.2.5 express-session

  • 세션 관리용 미들웨어이다.
  • 로그인 등의 이유로 세션을 구현하거나 특정 사용자를 위한 데이터를 임시적으로 저장해둘 때 매우 유용하다.
  • 세션은 사용자별로 req.session 객체 안에 유지된다.
  • express-session은 인수로 세션에 대한 설정을 받는다.
    • resave : 요청이 올 때 세션에 수정 사항이 생기지 않더라도 세션을 다시 저장할지 설정한다.
    • saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정한다.
  • express-session은 세션 관리 시 클라이언트에 쿠키를 보낸다.
  • cookie 옵션은 세션 쿠키에 대한 설정으로, maxAge, domain, path, expires, sameSite, httpOnly, secure 등 일반적인 쿠키 옵션이 모두 제공된다.
  • 세션을 한 번에 삭제하려면 req.session.destroy() 메서드를 호출한다.

 

6.2.6 미들웨어의 특성 활용하기

미들웨어는 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);
});

 

 

6.2.7 multer

  • 이미지, 동영상 등을 비롯한 여러 가지 파일을 멀티파트 형식으로 업로드할 때 사용하는 미들웨어이다.
  • 멀티파트 방식 : enctype이 multipart/form-data인 폼을 통해 업로드하는 데이터의 형식이다.
  • 다음과 같은 multipart.html이 있다면 멀티파트 형식으로 데이터를 업로드할 수 있다.
<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만 존재한다.

 

6.3 Router 객체로 라우팅 분리하기

  • app.js에서 app.get 같은 메서드가 라우터 부분이다.
  • 라우터를 많이 연결하면 app.js 코드가 매우 길어지므로 익스프레스에서는 라우터를 분리할 수 있는 방법을 제공한다.
  • routes 폴더를 만들고 그 안에 .js를 작성한다.
// 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 응답 미들웨어와 에러 처리 미들웨어를 연결해주는 것이 좋다.

 

6.4 req, res 객체 살펴보기

익스프레스의 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)을 지원하는 경우가 많은데, 메서드 체이닝을 활용하면 코드 양을 줄일 수 있다.

 

6.5 템플릿 엔진 사용하기

템플릿 엔진은 자바스크립트를 사용해서 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 표현

  • 기존 HTML과 다르게 화살괄호(<>)와 닫는 태그가 없다.
  • 탭 또는 스페이스로만 태그의 부모 자식 관계를 규명하는데, 자식 태그는 부모 태그보다 들여쓰기되어 있어야 한다. 들여쓰기에 오류가 있으면 제대로 렌더링되지 않는다.
  • 태그의 속성을 표현할 때 태그명 뒤에 소괄호로 묶어 적는다.
  • 속성 중 아이디와 클래스가 있는 경우에는 각각 #, .을 앞에 붙여서 표현한다.
  • HTML 텍스트는 속성 뒤에 한 칸을 띄고 입력한다.
  • 텍스트를 여러 줄 입력하고 싶은 경우 파이프(|)를 삽입한다.
  • style이나 script 태그로 CSS 또는 자바스크립트 코드를 작성할 경우 태그 뒤에 .을 붙인다.

 

6.5.1.2 변수

  • HTML과 다르게 자바스크립트 변수를 템플릿에 렌더링할 수 있다.
  • res.render을 호출할 때 보내는 변수를 퍼그가 처리한다.
  • res.render 메서드에 두 번재 인수로 변수 객체를 넣는 대신, res.locals 객체를 사용해서 변수를 넣을 수도 있는데, 템플릿 엔진이 res.locals 객체를 읽어서 변수를 집어넣는다. 이 방식은 현재 라우터뿐만 아니라 다른 미들웨어에서도 res.locals 객체에 접근할 수 있다는 장점이 있다.
  • 서버로부터 받은 변수는 다양한 방식으로 퍼그에서 사용 가능하다.
    • 변수를 텍스트로 사용하고 싶은 경우 태그 뒤에 =을 붙인 후 변수를 입력한다.
    • 속성에도 =을 붙인 후 변수를 사용할 수 있다.
    • 텍스트 중간에 변수를 넣으려면 #{변수}를 사용한다.
    • #{}의 내부와 = 기호 뒷부분은 자바스크립트로 해석하므로 자바스크립트 구문을 써도 된다.
  • 내부에 직접 변수를 선언할 수도 있다.
    • 빼기(-)를 먼저 입력하면 뒤에 자바스크립트 구문을 작성할 수 있다. 여기에 변수를 선언하면 다음 줄부터 해당 변수를 사용할 수 있다.
  • 퍼그는 기본적으로 변수의 특수 문자를 HTML 엔티티로 이스케이프(문법과 관련 없는 문자로 바꾸는 행위)하는데, 이스케이프를 원하지 않는다면 = 대신 !=을 사용한다.

 

6.5.1.3 반복문

  • HTML과 다르게 반복문도 사용할 수 있으며, 반복 가능한 변수인 경우에만 해당한다.
  • each로 반복문을 돌릴 수 있으며, each 대신 for을 사용할 수도 있다.
  • 반복문 사용 시 인덱스도 가져올 수 있다.

 

6.5.1.4 조건문

  • 조건문으로 편리하게 분기 처리할 수 있으며 if, else if, else를 사용할 수 있다.
  • case 문도 가능하다.

 

6.5.1.5 include

  • 다른 퍼그나 HTMl 파일을 넣을 수 있다.
  • header나 footer, 내비게이션처럼 웹을 제작할 때 공통되는 부분을 따로 관리할 수 있어 페이지마다 동일한 HTML을 넣어야 하는 번거로움을 없앤다.
  • include 파일 경로와 같은 형태로 사용한다.

 

6.5.1.6 extends와 block

  • 레이아웃을 정할 수 있으며, 공통되는 레이아웃 부분을 따로 관리할 수 있어 유용하다.
  • include와도 함께 사용할 수 있다.
  • 레이아웃이 될 파일에는 공통된 마크업을 넣되, 페이지마다 달라지는 부분을 block으로 비워둔다.
  • block은 여러 개 만들어도 되며, block [블록명]과 같은 형태로 block을 선언한다.
    • block이 되는 파일에서는 extends 키워드로 레이아웃 파일을 지정하고 block 부분을 넣는다.
    • block 선언보다 한 단계 더 들여쓰기되어 있어야 한다.
    • 나중에 익스프레스에서 res.render(’body’)를 사용해 하나의 HTML로 합쳐 렌더링할 수 있다.
    • 퍼그 확장자는 생략 가능하며 block 부분이 서로 합쳐진다.

 

6.5.2 넌적스

넌적스는 퍼그의 HTML 문법 변화에 적응하기 힘든 사람들에게 유용한 템플릿 엔진이다. 파이어폭스를 개발한 모질라에서 만들었으며, HTML 문법을 그대로 사용하되 추가로 자바스크립트 문법을 사용할 수 있다. 파이썬의 템플릿 엔진인 Twig와 문법이 상당히 유사한 넌적스는 npm i nunjucks 명령어를 사용하여 설치한다.

 

 

6.5.2.1 변수

  • res.render 호출 시 보내는 변수를 넌적스가 처리한다.
  • 넌적스에서 변수는 {{ }}로 감싼다.
  • 내부에 변수를 사용할 수도 있다.
    • 변수를 선언할 때는 {% set 변수 = '값' %}를 사용한다.
  • HTML을 이스케이프하고 싶지 않다면 {{ 변수 | safe }}를 사용한다.

 

6.5.2.2 반복문

  • 넌적스에서는 특수한 문을 {% %} 안에 쓰기 때문에 반복문도 이 안에 작성한다.
  • for in문과 endfor 사이에 위치한다.
  • 반복문에서 인덱스를 사용하고 싶다면 loop.index라는 특수한 변수를 사용할 수 있다.

 

6.5.2.3 조건문

  • 조건문은 {% if 변수 %} {% elif %} {% else %} {% endif %}로 구성되어 있다.
  • case문은 없지만 elif를 통해 분기 처리할 수 있다.

 

6.5.2.4 include

  • 다른 HTMl 파일을 넣을 수 있다.
  • header나 footer, 내비게이션처럼 웹 제작 시 공통되는 부분을 따로 관리할 수 있어 페이지마다 동일한 HTML을 넣어야 하는 번거로움을 없앤다.
  • include 파일 경로와 같은 형태로 사용한다.

 

6.5.2.5 extends와 block

  • 레이아웃을 정할 수 있으며, 공통되는 레이아웃 부분을 따로 관리할 수 있어 유용하다.
  • include와도 함께 사용할 수 있다
  • 레이아웃이 될 파일에는 공통된 마크업을 넣되, 페이지마다 달라지는 부분을 block으로 비워둔다.
  • block은 여러 개 만들어도 되며, {% block [블록명] %} 방식으로 선언하고 {% endblock %}으로 블록을 종료한다.
  • block이 되는 파일에서는 {% extends 경로 %} 키워드로 레이아웃 파일을 지정하고 block 부분을 넣는다.
  • 나중에 익스프레스에서 res.render('body')를 사용해 하나의 HTML로 합친 후 렌더링할 수 있고, 이후에 같은 이름의 block 부분이 서로 합쳐진다.

 

6.5.3 에러 처리 미들웨어

에러 처리 미들웨어는 error라는 템플릿 파일을 렌더링하는데, 렌더링 시 res.locals.message와 res.locals.error에 넣어준 값을 함께 렌더링한다. 또한, res.render에 변수를 대입하는 것 외에도 res.locals 속성에 값을 대입해 템플릿 엔진에 변수를 주입할 수 있다.

error 객체의 스택 트레이스시스템 환경이 production(배포 환경)이 아닌 경우에만 표시된다. 배포 환경인 경우에는 에러 메시지만 표시되는데, 이는 에러 스택 트레이스가 노출되면 보안에 취약할 수 있기 때문이다.

 


Quiz

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

728x90

관련글 더보기