Node.js

[Node.js] #6 익스프레스 웹 서버 만들기

lgvv 2023. 7. 23. 14:25

익스프레스 웹 서버 만들기

 

 

#4에서 서버 만들기 불편했을거다. 이제 좀 쉬워지니까 알아보자.

 

 

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

우선 learn-express를 만들어보자.

npm init -y

아래처럼 수정하자.

{
  "name": "learn-express",
  "version": "0.0.1",
  "description": "익스프레스를 배워보자",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

그 다음에는

npm i express
npm i -D nodemon

 

script가 생성되는데 이 부분에 반드시 start 속성을 넣어주어야 한다. nodemon app을 하면 app.js를 nodemon으로 실행한다는 실행한다는 뜻이다.

 

콘솔에 rs를 입력해서 수동으로 재시작도 가능.

nodemon은 개발용으로만 사용할 것을 권장.

 

서버의 역할을 할 app.js을 작성

const express = require('express');

const app = express();
app.set('port', process.env.PORT || 3000); // env에 포트값 있으면 사용하고아니면 3000번
// app.set(키, 값) 통해서 데이터 저장
// app.get(키, 값)으로 나중에 읽음

app.get('/', (req, res) => { 
    res.send('Hello World');
});

app.listen(app.get('port'), () => { 
    console.log(app.get('port'), '빈 포트에서 대기중');
});

 

 

위에를 html 페이지를 넣어서 바꿔보자.

 

// index.html
<html>
    <head>
        <meta charset="UTF-8" />
        <title>익스프레스 서버</title>
    </head>
    <body>
        <h1>Express</h1>
        <p>배워봅시다.</p>
    </body>
</html>

// app.js
const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);

app.get('/', (req, res) => { 
    // res.send('Hello World');
    res.sendFile(path.join(__dirname, 'index.html'));
});

app.listen(app.get('port'), () => { 
    console.log(app.get('port'), '빈 포트에서 대기중');
});

크크 학교 다닐 때 외에 이걸 다시 보다니 영롱하다!

 

영롱하다!

 

# 자주 사용하는 미들웨어

 

미들웨어는 익스프레스의 핵심이다.

 

미들웨어는 app.use와 함께 사용된다.

// app.js
const express = require('express');
const path = require('path');

const app = express();
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'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

app.use에서는 req, res, next함수를 넣으면 된다. 미들웨어는 위에서부터 아래로 순서대로 실행되면서 요청과 응답 사이에 특별한 기능을 추가할 수 있음. 이번에는 next라는 새번째 매개변수를 사용했는데, 다음 미들웨어로 넘어가는 함수. next를 실행하지 않으면 다음 미들웨어가 실행되지 않음.

 

주소를 첫번째 인수로 넣어주지 않는다면 미들웨어는 모든 요청에서 실행되고, 주소를 넣는다면 해당하는 요청에서만 실행된다고 봄.

 

app.use(미들웨어): 모든 요청에서 미들웨어 실행

app.use('/abc', 미들웨어): abc로 시작하는 요청에서 미들웨어 실행

app.post('/abc', 미들웨어): abc로 시작하는 POST 요청에서 미들웨어 실행

 

app.use나 app.get 같은 라우터에 미들웨어를 여러개를 장착할 수 있다.

app.get 라우터에 미들웨어가 두 개 연결되어 있음. 다만 이때도 next를 호출해야 다음 미들웨어로 넘어갈 수 있다.

 

에러처리!! 매개변수는 네 개다. 모든 매개변수를 사용하지 않더라도 매개변수가 반드시 네 개여야 한다. 

res.status 메서드로 HTTP 상태 코드를 지정할 수 있따. 기본값은 200이며 실무에서는 직접 에러 처리 미들웨어를 연결해주는게 좋다. 에러처리 미들웨어는 특별한 경우 아니면 가장 아래에 위ㅊ하도록 한다.

 

미들웨어를 통해 요청과 응답에 대한 기능을 추가할 수 있고, 실무에 자주 사용하는 패키지들을 설치해봅시다.

npm i morgan cookie-parser express-session dotenv

 

dotenv 패키지는 .env 파일을 읽어서 precess.env로 만듭니다. process.env.COOKIE_SECRET에 cookiesecret 값이 할당된다. 

키=값 형식으로 추가하면 된다. process.env를 별도의 파일로 관리하는건 보안과 설정의 편의성

비밀 키들을 소스 코드에 그대로 적어두면 소스 코드가 유출되었을 때도 키도 같이 유출 따라서 .env 같은 별도의 파일에 비밀키를 적어두고 관리함 소스코드가 유출되더라도 .env파일만 잘 관리하면 지킬 수 있다.

 

// .env
COOKIE_SECRET=cookiesecret

 

아래 코드를 작성해보자

// app.js
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const path = require('path');

dotenv.config(); // ✅ 설정
const app = express();
app.set('port', process.env.PORT || 3000);

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',
}));

const multer = require('multer');
const fs = require('fs');

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads 폴더가 없어 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);
      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});
app.get('/upload', (req, res) => {
  res.sendFile(path.join(__dirname, 'multipart.html'));
});
app.post('/upload', upload.single('image'), (req, res) => {
  console.log(req.file);
  res.send('ok');
});

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'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

# morgan

morgan 연결 후, 기존 로그 외에 추가적인 로그를 볼 수 있음.

3000번 포트에서 대기 중
모든 요청에 다 실행된다.
GET / 요청에서만 실행됩니다.
Error: 에러는 에러 처리 미들웨어로 갑니다.
// 에러 스택 트레이스 생략
GET / 500 6.332 ms - 50

 

맨 아래 로그는 morgan 미들웨어에서 나오는 것이다. 요청과 응답에 대한 정보를 콘솔에 기록

morgan 미들웨어는 다음과 같이 사용

app.use(morgan('dev'));

 

인수로 dev 외에 combined, common, short, tiny 등을 넣을 수 있다. 

개발환경은 dev, 배포환경은 combined

 

 # static

static 미들웨어는 정적인 파일들을 제공하는 라우터 역할을 한다.

기본적으로 제공되기에 따로 설치할 필요 없이 express 객체 안에서 꺼내 장착하면 된다.

 

app.use('요청 정보', express.static('실제 경로'));

app.use('/', express.static(path.join(__dirname, 'public'));

 

함수의 인수로 정적 파일들이 담겨 있는 폴더를 지정하면 됩니다. 

예를 들어 public/stylesheet/style.css는 http://localhost:3000/stylesheets/style.css로 접근할 수 있음.

 

실제 서버의 폴더 경로에는 public이 들어 있지만 요청 주소에는 없는데 요청 경로가 다르므로 외부인이 서버의 구조를 쉽게 파악할 수 있음.

 

또한, fs.readFile을 파일을 직접 읽어서 전송할 필요가 없음. 만약 요청 경로에 해당하는 파일이 없으면 알아서 내부적으로 next를 호출 파일 발견하면 미들웨어는 실행안돼 응답으로 파일 보내고 next를 호출하지 않음.

 

 #  body-parser

요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어준다. 

보통 폼 데이터나 AJAX 요청의데이터를 처리. 단, 멀티파트(이미지, 동영상, 파일) 데이터 처리하지 못함.

이 경우에는 뒤에 나오는 multer 모듈을 사용하면 된다.

 

body-parser는 다음과 같이 사용하면 된다.

app.use(express.join());
app.use(express.urlencoded({ extended: false }));

 

 

아래는 요청 데이터의 종류다.

const bodyParser = require('body-parser');

app.use(bodyParser.raw());
app.use(bodyParser.text());

요청 데이터 종류에 대해 살펴보자.

JSON은 JSON형식에 따라 URL-encoded는 주소 형식으로 데이터를 보내는 방식 폼 전송은 URL-encoded 방식을 주로 사용

urlencoded 메서드를 보면 { extended: false }라는 옵션이 들어 있다.

이 옵션이 false이면 노드의 querystring 모듈을 사용해 쿼리스트링을 해석하고, qs 모듈은 npm을 통해 사용하는 모듈

 

POST와 PUT요청에서 본문 받으면 req.on('data') req.end('data) 했던거 기억남?

바디 파서 사용하면 안해도 돼

 

 #  cookie-parser

요청에 동봉된 쿠리를 해석해 req.cookies 객체로 만든다.

 

app.use(cookieParser(비밀 키));

 

예를들어 name=lgvv 쿠키를 보냈다면, { name: 'lgvv' }가 된다. 유효 기간이 지난 쿠키는 알아서 걸러낸다.

첫번째인수로 비밀키를 넣어줄 수 있다.

 

res.cookie('name', 'lgvv', { 
	expires: new Date(Date.now() + 90000),
    httpOnly: true,
    secure: true,
});

res.clearCookie('name', 'lgvv', { httpOnly: true, secure: true });

 

 쿠키를 지우려면 키 값 외에 옵션마저도 정확히 일치해야 만드렁진다. 단 expires나 maxAge 옵션은 일치할 필요 없음.

옵션 중에는 signed라는 옵션이 있는데, 이를 true로 설정하면 쿠키 뒤에 서명이 붙는다. 내 서버가 쿠키를 만들었다는 것을 검증할 수 있으므로 대부분의 경우 서명 옵션을 켜두는 것이 좋다. 서명을 위한 비밀 키는 cookieParser 미들웨어에 인수로 넣은 process.env.COOKIE_SECRET이 됩니다.

 

 #  express-session

세션 관리용 미들웨어. 로그인 등의 이유로 세션을 구현하거나 특정 사용자를 위한 데이터 임시적으로 저장해둘 때 매우 유용. 세션은 사용자별로 req.session 객체 안에 유지된다.

 

app.use(session({
	resave: false, // ✅ 요청이 들어올 때 세션에 수정사항 생기지 않더라도 세션을 다시 저장할지 설정
    saveUninitialized: false, // ✅ 세션에 저장할 내역이 없더라도 세션을 재시작할지 설정
    secret: process.env.COOKIE_SECRET, // ✅ 세션 관리시 클라이언트에 쿠키
    cookie: { // ✅ 일반적인 쿠키 옵션을 제공
    	httpOnly: true, // ✅ 클라이언트에서 쿠키 확인 못하게
        secure: false, // ✅ https적용여부
    },
	name: 'session-cookie', // ✅ env 값
}));

예제에는 없지만 store라는 옵션이 있는데 메모리가 초기화되어 세션이 모두 사라지더라도 store에 데베를 연결해 세션을 유지하는것이 좋다.

보통 레디스가 쓰임.

store.session.name = 'lgvv'; // 세션 등록
req.sessionID; // 세션 아이디 확인
req.session.destroy(); // 세션 모두 제거

 

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

미들웨어를 직접 만들어보기도 했고, 다른 살마이 만든 미들웨어 패키지를 설치해 장착해보기도 함.

이번에는 그 특성을 정리해 볼 예정.

 

app.use((req, res, next) => { 
	console.log('모든 요청에 다 실행된다.');
    next();
});

미들웨어는 req, res, next를 갖는 함수로써 app.use나 app.get, app.post 등으로 장착한다. 특정한 주소의 요청에만 미들웨어가 실행되게 하려면 첫번째 인수로 주소를 넣으면 된다.

 

app.use(
	morgan('dev');
    express.static('/', path.join(__dirname, 'public');
	express.json();
    express.urlencoded({ extended: false }),
    cookeParser(precess.env.COOKIE_SECRET),
);

 

위와 같이 여러개의 미들웨어를 장착할 수 있으며, 다음 미들웨어로 넘어가려면 함수를 호출해야 한다.

위 미들웨어들은 내부적으로 next()를 호출하고 있으므로 연달아 쓸 수 있다.

 

next를 호출하지 않는 미들웨어는 res.send나 res.sendFile 등의 메서드로 응답으 보내야 한다.

 

express.static과 같은 미들웨어는 정적 파일을 제공할 때 next 대신 res.sendFile 메서드로 응답을 보냅니다.

따라서 정적 파일을 제공하는 경우 expree.json이나 express.urlencoded, cookieParser 미들웨어는 실행되지 않는다.

 

미들웨어 장착 순서에 따라 어떤 미들웨어는 실행되지 않을수도 있다.

next에 인수를 넣을수도 있음

next('route')라는 인수를 넣으며 다음 라우터의 미들웨어로 바로 이동하고, 그 외의 인수를 넣는다면 바로 에러 처리 미들웨어로 이동.

 

next(error) 다음 에러 핸들러로 보냄

 

(err, req, res, enxt) => { }

 

미들웨어 간 데이터를 전달하는 방법도 있음. 세션을 사용한다면 req.session 객체에 데이터를 넣어도 되지만, 세션이 유지되는 동안에 데이터도 계속 유지된다는 단점이 있다.

만약 요청이 끝날 때 까지만 데이터를 유지하고 싶다면 res.locals 객체에 데이터를 넣어두면 된다.

app.use((req, res, next) => { 
	res.locals.data = '데이터 넣기';
    next();
}, (req, res, next) => { 
	console.log(res.locals.data); // 데이터 받기
    next();
});

요청이 처리되는 동안 res.locals 객체를 통해 미들웨어 간에 데이터를 공유할 수 있음.

새로운 요청이 들어오면 res.locals는 초기화 된다.

 

app.set과의 res.locals의 차이.

app.get, req.app.get으로 어디서든지 데이터를 가져올 수 있음. 하지만 app.set을 하지 않고서 res.locals 객체에 데이터를 넣어서 미들웨어로 전달하는 이유가 있음. app.set은 익스프레스에서 전역적으로 사용되므로 하나의 요청 안에서만 유지되어야 하는 값을 넣기에는 부적절하다. app.set은 앱 전체의 설정을 공유할 때 사용하면 된다.

res.locals 객체는 하나의 요청 안에서만 유지되므로 res.locals 객체를 통해 요청에 종속되는 데이터를 전달하는 것이 좋다.

 

미들웨어 안에 미들웨어를 넣는 유용한 방식

app.use(morgan('dev'));
// or
app.use((req, res, next) => { 
	morgan('dev')(req, res, next);
});

 

이 패턴이 유용한건 기존 미들웨어 기능 확장 예를 들어 분기 처리도 가능

 

아래코드 보자

 

app.use((req, rex, next) => { 
	if (precess.env.NODE_ENV === 'production') { 
    	morgan('combined')(req, res, next);
    } else { 
    	morgan('dev')(req, res, next);
    }
});

 

조건문에 따라 분기도 가능

 

 #  multer

 

이건 다소 어려운데 이미지나 영상 등을 비롯한 멀티파트 이미지 파일을 업로드할 떄 사용하는 미들웨어

<form id="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>

이건 직접 파싱하기 까다로워서 multer 미들웨어 사용하면 편리함.

 

npm i multer

 

multer 패키지 안에는 여러 종류의 미들웨어가 들어있다.

const multer = require('multer');

const upload = multer({
	storage: multer.diskStorage({ 
    	destination(req, file, done) { // ✅ 어떤 이름으로 어디에 저장할지
        	done(null, 'uploads/'); // ✅ 에러가 있다면 error를 넣기, 두번째 인수는 실제 경로나 파일이름
        },
        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 }, // ✅ 5MB로 용량제한
});

위 코드 올바르게 작동하려면 upload 폴더가 반드시 존재해야 한다.

없다면 직접 만들어주거나 fs모듈 사용해서 서버 생성할 때 생성해야 한다.

 

const fs = require('fs');

try {
	fs.readdirSync('uploads');
} catch (error) { 
	console.error('uploads 폴더가 없어 폴더를 생성');
    fs.mkdirSync('uploads');
}

설정이 끝나면 upload 변수가 생기는데, 여기에 다양한 종류에 미들웨어가 있다.

먼저 파일을 하나만 업로드하는 경우 싱글 미들웨어를 사용한다.

app.post('/upload', upload.single('image'),  (req, res) => { 
 	console.log(req.file, req.body);
    res.send('ok');
});

single 미들웨어를 라우터 미들웨어 앞에 넣어두면 multer 설정에 따라 파일 업로드 후 req.file 객체가 생성된다.

 

req.file 객체는 아래처럼 생김

 

멀티파트폼에서

이번에 미들웨어는 single 대신 array를 사용해보자

app.post('/upload', upload.array('many'),  (req, res) => { 
 	console.log(req.file, req.body);
    res.send('ok');
});

 

업로드 결과도 req.file대신 req.files 배열에 들어 있다.

파일을 여러 개 업로드하지만 input 태그나 폼 데이터의 키가 다른 경우에는 fileds 미들웨어를 사용한다

<form id="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>

fields 미들웨어의 인수로 input 태그의 name을 각각 적는다.

 

app.post('/upload',
	upload.fields([{ name: 'image1' }, { name: 'image2' }]),  (req, res) => { 
 	console.log(req.file, req.body);
    res.send('ok');
});

업로드 결과도 req.files.image1, req.files.image2로 따로 들어있음.

 

특수한 경우지만 파일 없이도 멀티파트 형식으로 올리기도 함

 

app.post('/upload', upload.none(),  (req, res) => { 
 	console.log(req.body);
    res.send('ok');
});

파일 업로드하지 않아서 바디만 존재한다.

 

실제로 multer예제 실습하려면 app.js수정

 

// multipart.html
<form id="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.js
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const path = require('path');

dotenv.config();
const app = express();
app.set('port', process.env.PORT || 3000);

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',
}));

const multer = require('multer');
const fs = require('fs');

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads 폴더가 없어 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);
      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});
app.get('/upload', (req, res) => {
  res.sendFile(path.join(__dirname, 'multipart.html'));
});
app.post('/upload', upload.single('image'), (req, res) => {
  console.log(req.file);
  res.send('ok');
});

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'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

잘 나온다.

 

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

 

요청 메서드와 주소별로 분기 처리 하느라 코드가 매우 복잡

if문으로 분기하면서 코딩했기 때문에 보기도 어려움.

app.js에서 app.get 같은 메서드가 라우터 부분이다. 라우터를 많이 연결하면 app.js 코드가 매우 길어지므로 익스프레스에서는 라우터를 분리할 수 있는 방법을 제공

routes 폴더를 만들고 그 안에 index.js와 user.js를 작성

 

/// routes/index.js
const express = require('express');

const router = express.Router();

// GET / 라우터
router.get('/', (req, res) => {
  res.send('Hello, Express'); // ✅ 이거를 보낸다
});

module.exports = router; // ✅ 이거 달아줘야 한다.

///routes.user.js
const express = require('express');

const router = express.Router();

// GET /user 라우터
router.get('/', (req, res) => {
  res.send('Hello, User'); // ✅ 이거를 보낸다
});

module.exports = router; // ✅ 이거 달아줘야 한다.

/// app.js
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const path = require('path');

dotenv.config();
const indexRouter = require('./routes'); // ✅ 연결할 경로
const userRouter = require('./routes/user'); // ✅ 연결할 경로

const app = express();
app.set('port', process.env.PORT || 3000);

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('/', indexRouter); // ✅ url 설정
app.use('/user', userRouter); // ✅ url 설정

app.use((req, res, next) => {
  res.status(404).send('Not Found');
});

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send(err.message);
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

이렇게 만들어진 index.js와 user.js를 app.use를 통해 app.js에 연결한다

에러처리 미들웨어 위에 404상태 코드 응답하는 미들웨어를 추가

 

indexRouter를 ./routes로 require할 수 있는 이유는 index.js는 생략할 수 있음

require('./routes/index.js')와 require('routes')와 같다

 

이전절에서 next('route') 알아보자

이 기능은 라우터에 연결된 나머지 미들웨어들을 건너뛰고 싶을때 사용.

 

router.get('/', (req, res, next) => { 
	next('rotue');
}, (req, res, next) => { 
	console.log('실행되지 않습니다');
    next();
}, (req, res, next) => { 
	console.log('실행되지 않습니다');
    next();
});

router.get('/', (req, res) => { 
	console.log('실행된다.');
	res.send('hello express');
});

라우터 여러개튼 next를 호출하면 다음 미들웨어 실행된다.

첫번째 라우터의 next()대신 next('route')를 호출해서 대신 이 주소와 일치하는 걸로 이동

 

라우터 주소에는 정규표현식을 비롯한 특수 패턴을 사용할 수 있음.

여러 가지 패턴이 있지만, 자주 쓰이는 패턴 하나만 알아보자.

 

router.get('/user/:id', (res, req) => { 
	console.log(req.params, req.query);
});

문자 그대로 :id를 의미하는 것이 아님. 이 부분에는 다른 값을 넣을 수 있음

예를 들면 /user/123, /user/1 등의 요청도 이 라우터가 처리함.

이 방식의 장점은 :id이면 해당하는 1이나 123을 조회할수 있다는 점 req.params 객체 안에 들어 있다.

:id이면 req.params.id로 :type이면 req.params.type으로 조회할 수 있다.

 

단, 이 패턴 사용할 때 주의점. 일반 라우터보다 뒤에 위치해야 함.

다양한 라우터를 아우르는 와일드 카드 역할이라 일반 라우터 보다 뒤에 있어야 방해하지 않음.

router.get('/user/:id', (req, res) => { 
	console.log('얘만 실행됩니다.');
});

router.get('/user/like', (req, res) => { 
	console.log('실행되지 않는다.');
});

/user/like와 같은 라우터는 /user/:id 같은 라우트 매개변수 위에 두어야 함.

주소에 쿼리스트링 쓰는 경우도 있음.

 

/user/123?limit=5&skip=10

req.params와 req.query객체는 다음과 같음

{ id: '123' } { limit: '5', skip: '10' }

app.js에서 에러 처리 미들웨어 위에 넣어둔 미들웨어는 일치하는 라우터가 없을 때 404상태 코드를 응답하는 역할.

 

app.use((req, res, next) => { 
	res.status(404).send('Not Found');
});

 

이 미들웨어를 제거하고 localhost:3000/abc에 접속하면 404상태와 함께 메시지가 응답된다.

 

라우터에서 자주 쓰이는 활용법으로 app.route나 router.route가 있다.

router.get('/abc', (req, res) => { 
	res.send('GET /abc');
});

router.post('/abc', (req, res) => { 
	res.send('POST /abc');
});

// 아래처럼 묶기도 가능
router.route('/abc')
	.get((req, res) => { 
		res.send('GET /abc');
	}).post((req, res) => { 
		res.send('POST /abc');
	});

 

 # req, res 객체 살펴보기

 

익스프레스의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것이다. 기존 http 모듈의 메서드도 사용할 수 있고, 익스프레스가 추가한 메서드나 속성을 사용할 수도 있다.

 

 

체이닝을 통해 코드양을 줄일 수 있다.

res
	.status(201)
    .cookie('test', 'test')
    .redirect('/admin');

 

 # 템플릿 엔진 사용하기

 

HTML은 정적임. 그래서 대표적 템플릿엔진인 pug와 넌적스 알아볼 예정.

 

 

 # 퍼그(제이드), 넌적스는  책을 별도로 살펴보자.

그렇게 어렵지 않음. 문법에 대한 부분.

 

예전 이름인 제이드(Jade)로 더 유명함. 루비 사용해 보았다면 문법이 비슷해서 빠르게 적응할 수 있음.

 

퍼그 설치하기

npm i pug

 

익스프레스와 연결하려면 다음 부분이 있어야 함

/// app.js

...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); // ✅
app.set('view engine', 'pug'); // ✅

app.use(morgan('dev'));
...

 

 

views는 템플릿 파일들이 위치한 폴더를 지정하는 것. res.render 메서드가 이 폴더 기준으로 템플릿 엔진을  찾아서 렌더링 함.

re.render('index')라면 views/index.pug를 렌더링한다. res.render('admin/main')이라면 views/admin/main.pug를 렌더링

 

view engine은 어떠한 종류의 템플릿 엔진을 사용할지를 나타낸다. 현재 pug로 설정되어 있으므로 그대로 사용하면 안된다.

 

 

 

 

'Node.js' 카테고리의 다른 글

[Node.js] 몽고디비  (0) 2023.08.10
[Node.js] #7 MySQL  (0) 2023.07.23
[Node.js] #5 패키지 매니저  (0) 2023.07.23
[Node.js] #4 http 모듈로 서버 만들기  (0) 2023.07.22
[Node.js] #3 노드 기능 알아보기  (0) 2023.07.22