ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Node.js 스터디 5주차
    BackEnd/Node.js 2022. 2. 15. 18:54

    Node.js 교과서 6장 익스프레스 웹 서버 만들기


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

    Express 모듈 : Http 모듈의 요청과 응답 객체에 기능 및 메서드 추가해서 HTTP모듈보다 더 편리하게 사용이 가능

     

    npm i express 명령어를 통해 Express를 설치할 수 있다. 

    const express = require('express');
    const app = express(); //express 객체 생성
    app.set('port', process.env.PORT || 3000); //포트 번호 설정
    app.get('/', (req, res) => { // '/'에 get요청 올 때 어떤 처리를 할지 작성
    	res.send('Hello, Express'); // http모듈의 res.write, res.end대신 res.send사용
    }); //Get외에도 POST, PUT, DELETE, PATCH등 다른 메서드 사용 가능
    
    app.listen(app.get('port'), () => {
    	console.log(app.get('port'), '번 포트에서 대기 중');
    });

    npm start로 실행하면 콘솔창엔 위 사진처럼 나온다. 

    아래는 localhost:3000 접속시 나오는 화면이다. res.send에 써놓은 문자열 Hello, Express가 나오는걸 확인할 수 있다. 

     

    만약 HTML파일로 응답하고 싶다면 path모듈로 위치를 지정한 다음 응답으로 html파일을 보내주면 된다. 

    * index.html파일 코드

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

    app.js 에는 아래처럼 코드를 추가한다. 

    const express = require('express');
    const path = require('path'); // path 모듈 
    
    const app = express(); 
    app.set('port', process.env.PORT || 3000);
    
    app.get('/', (req, res) => { 
    	res.sendFile(path.join(__dirname, '/index.html')) //sendFile로 메서드 변경
    }); 
    
    app.listen(app.get('port'), () => {
    	console.log(app.get('port'), '번 포트에서 대기 중');
    });

    - 위처럼 html파일의 내용이 나오는 것을 확인할 수 있다. 

     


     

    6.2 자주 사용하는 미들웨어

     

    출처 : https://expressjs.com/ko/guide/using-middleware.html

    익스프레스 사이트에 가보면 미들웨어 관련 설명이 나와있다. 

    미들웨어가 요청과 응답의 중간에 위치해있어서 미들웨어라 부르는데 라우터, 에러 핸들러, 다음 미들웨어 호출 등 익스프레스의 핵심이라고 말할 수 있다. app.use(미들웨어)의 형태로 사용한다. 

     

    ...
    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'), '번 포트에서 대기 중');
    });

     

    * 경로를 안넣는 경우 모든 요청에서 실행, 주소 넣는 경우 해당 요청에서만 실행

    * next를 호출해야 다음 미들웨어로 넘어간다. 

    * 대부분의 에러 처리 미들웨어는 특수경우 제외하고는 맨 아래에 두는 것이 좋다. 

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

    dotenv : env 파일을 읽어서 process.env 로 만든다 .env에는 주로 key파일들을 담아둔다. (db 비밀번호 등)

    npm i morgan cookie-parser express-session dotenv
    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')); // combined, common, short, tiny등의 인수 가능
    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',
    }));
    
    ...
    COOKIE_SECRET=cookiesecret

     

     body-parser : req.body객체로 만들어주는 미들웨어이다. 추가적으로 이미지 등 multipart data가 있는 경우는 multer라는 모듈을 사용하면 된다. 

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

    익스프레스 4.16.0버전부터 body-parser 미들웨어의 일부 기능이 익스프레스에 내장이 되어있다. 그렇지만 Raw, Text 형식의 데이터를 추가로 해석할 수 있어서 직접 설치하는 경우도 있다. 

     

     cookie-parser : 요청에 동봉된 쿠키를 해석해서 req.cookies 객체로 생성한다. 

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

    쿠키를 생성하려는 경우에는 res.cookie, res.clearCookie등의 메서드 사용한다. 

    res.cookie('name', 'zerocho', { // res.cookies(키, 값, 옵션 형식)
    	expires : new Date(Date.now() + 900000),
        httpOnly : true,
        secure : true,
    });
    res.clearCookie('name', 'zerocho', { httpOnly : true, secure : true }); //옵션도 일치해야 쿠키 지워짐

     

    ✅ express-session : 세션 관리용 미들웨어, 특정 사용자를 위한 데이터 임시적으로 저장해두거나 로그인 등을 위한 세션 구현시 사용한다. 사용자별로 req.session객체에 유지. express-session은 세션 관리시 클라이언트에 쿠키 보냄. 

    app.use(session({
        resave : false, //수정 사항 안생겨도 세션 다시 저장할지 설정
        saveUninitialized : false, //처음부터 세션 생성할지 설정
        secret : process.env.COOKIE_SECRET,
        cookie : {
        	httpOnly : true,
            secure : false,
        },
        name : 'session-cookie',
    }));

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

    - 미들웨어는 req, res, next를 매개변수로 가지는 함수(에러처리 미들웨어만 예외적으로 err, req,res, next)

    - app.use, app.get, app.post등으로 장착

    - next() 안에 인수 넣기 가능('route' : 다음 라우터로, error : 에러 핸들러로)

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

    - 요청이 끝날 때까지만 데이터를 유지하려면 위 코드처럼 req객체에 데이터 넣어두면 된다. 

    - app.set의 경우 익스프레스 전역적으로 사용하기 때문에, 사용자 개개인 값 넣어두는 것은 req객체를 사용한다. 

     

    ✅ 미들웨어 사용 패턴

    - 미들웨어 안에 미들웨어 넣는 방식

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

    - 분기처리(조건문)따라 미들웨어 적용

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

     

     multer : 이미지, 동영상 등 여러 파일을 멀티파트 형식(multipart/form-data)으로 업로드 시 사용

    " npm i multer" 을 통해 multer 패키지 설치

    <form id = "form" action = "/upload" method = "post" enctype="multipart/form-data">
        <input type = "file" name = "image" />
        <input type = "text" name = "title" />
        <button type="submit">업로드</button>
    </form>

     

    multer 패키지의 기본 설정은 아래와 같다. 단 아래 코드는 서버 내에 uploads폴더가 있어야 한다. 

    const multer = require('multer');
    const upload = multer({
    	storage : multer.diskStorage({ 
        	destination(req, file, done) { //destination : 어디에
            	done(null, 'uploads/');
            },
            filename(req, file, done) { // filename : 어떤 이름으로 저장
            	const ext = path.extname(file.originalname);
                done(null, path.basename(file.originalname, ext) + Date.now() + ext);
            },
         }),
         limits : { fileSize : 5 * 1024 * 1024 },
    });

    없는 경우 fs 모듈의 mkdirSync 메소드로 서버 시작할 때 생성해줄수도 있다. 

    const fs = require('fs');
    
    try {
    	fs.readdirSync('uploads');
    } catch (error) {
    	console.error('uploads 폴더가 없어 uploads폴더 생성합니다.');
        fs.mkdirSync('uploads');
    }

     

    - 파일 하나만 업로드 하는 경우에는 single미들웨어 사용

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

    - 여러개 업로드 하는 경우

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

    - multiple을 input태그 안에 작성

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

    - single대신 array로 미들웨어 교체한다. upload결과도 req.files라는 배열로 바뀐것을 코드에서 확인할 수 있다.

     

    - 파일이 여러개지만 태그나 폼 데이터 키가 다른 경우

    <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.post('/upload', 
        upload.fields([{ name : 'image1'} , { name : 'image2'}]),
        (req, res) => {
            console.log(req.files, req.body);
            res.send('ok');
        },
    );

    - fileds의 매개변수로 input 태그의 name을 각각 적는다. 

     

    - 그 외 특수경우로 파일 업로드 안하고 멀티파트 형식으로 업로드 하는 경우에 none미들웨어 사용

    <form id = "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);
        res.send('ok');
    });

    ✅ multer의 미들웨어를 정리하면 아래와 같다. 

    single 이미지 하나인 경우, req.file로 나머지는 req.body로
    array 이미지 여러개를 req.files로
    나머지는 req.body로
    fields
    none 모든 정보를 req.body로

     

    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.fields([{ name : 'image1'} , { name : 'image2'}]),
        (req, res) => {
            console.log(req.files, req.body);
            res.send('ok');
        },
    );

     


     

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

    app.get같은 메서드 부분이 라우터 부분이다. routes폴더를 생성하고 그 안에 index.js, user.js 작성

     

    - index.js

    const express = require('express');
    const path = require('path');
    const router = express.Router();
    
    router.get('/', (req, res) => {
        res.send('Hello, Express');
    });
    
    module.exports = router;

     

    - user.js 

    const express = require('express');
    const router = express.Router();
    
    router.get('/', (req, res) => {
        res.send('Hello, User');
    });
    
    module.exports = router;

     

    - app.js

    라우터를 연결하고 404 응답 미들웨어도 작성

    const indexRouter = require('./routes');
    const userRouter = require('./routes/user');
    
    // 라우터 연결
    app.use('/', indexRouter);
    app.use('/user', userRouter);
    
    // 404리턴 미들웨어
    app.use((req, res, next) => {
        const error = new Error(`${req.method} ${req.url} 라우터가 없습니다. `);
        error.status = 404;
        next(error);
    })

    위 이미지처럼 경로를 다르게 접속하니 각각에 해당하는 응답을 받을 수 있다. 

    router.get('/', function(req, res, next) {
    	next('route'); // 다음 미들웨어 실행. 
    }, function(req, res, next) {
    	console.log('실행되지 않습니다.');
        next();
    }, function(req, res, next) {
    	console.log('실행되지 않습니다. ');
        next();
    });
    router.get('/', function(req, res) {
    	console.log('실행됩니다');
        res.send('Hello, Express');
    }); 
    router.get('/user/:id', function(req, res) { //users/12등 요청도 처리
    	console.log(req.params, req.query);
    });

     

    * /user/like와 같은 라우터는 라우터 매개변수 사용하는 라우터보단 위에 위치해야 한다. 

    * 주소는 같으나 메서드가 다른 경우, 아래의 코드처럼 처리가 가능하다. 

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

    6.4 req, res 객체 살펴보기

     

    ✅ Http모듈의 req, res객체가 확장된 것이 Express객체

    req 객체

    req.app req 객체를 통해 app객체에 접근할 수 있음
    req.body body-parser미들웨어가 만드는 요청의 본문 해석
    req.cookies cookie-parser 미들웨어가 만드는 요청의 쿠키 해석
    req.ip 요청의 ip주소 담겨있음
    req.params 라우트 매개변수에 대한 정보가 담긴 객체
    req.query 쿼리 스트링에 대한 정보가 담김
    req.signedCookies 서명된 cookie는 이곳에 담긴다.
    req.get(헤더이름) 헤더의 값을 가져오고 싶을 때 사용하는 메서드

    res 객체

    res.app res객체를 통해 app객체에 접근할 수 있음
    res.cookie(키, 값, 옵션) 쿠키를 설정하는 메서드
    res.clearCookie(키, 값,옵션) 쿠키를 제거하는 메서드
    res.end() 데이터 없이 응답 보내는 경우
    res.json(JSON) json형식의 응답을 보냄
    res.redirect(주소) 리다이렉트할 주소와 함께 응답을 보냄
    res.render(뷰, 데이터) 템플릿 엔진 렌더링해서 응답시 사용
    res.send(데이터) 데이터와 함께 응답을 보냄. 
    res.sendFile(경로) 경로에 위치한 파일을 응답
    res.set(헤더, 값) 응답의 헤더를 설정
    res.status(코드) 응답시의 HTTP상태 코드 지정

    아래의 예시처럼 사용할 수 있다.

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

     


     

    6.5절 템플릿 엔진 사용하기

     

    ✅ 퍼그

    - 문법이 간단하지만 HTML과는 문법이 많이 달라 호불호가 있음

    - npm i pug 로 설치

    app.set('views', path.join(__dirname, 'views')); //템플릿 파일들이 위치한 폴더 지정
    app.set('view engine', 'pug');

    - layout.pug

    doctype html
    html	
    	head
        	title=title
            link(rel='stylehseet', href='/style.css')
        body
        	block content

    - index.pug

    extends layout
    
    block content
    	h1 = title
        p Welcome to #{title}

    -error.pug

    extends layout 
    block content 
        h1 = message
        h2 = error.status 
        pre #{error.stackS}

     

    ✅ 넌적스

    - HTML문법을 그대로 사용하되, 추가로 자바스크립트 문법 사용가능

    - 파이썬 템플릿 엔진 Twig와 문법이 유사

    - npm i nunjucks로 설치

    const nunjucks = require('nunjucks');
    ...
    nunjucks.configure('views', {
        express: app, // express속성에 app객체 연결
        watch : true, //html변경시 템플릿 엔진 다시 렌더링
    });

     

    퍼그, 넌적스 문법 비교

    퍼그 특성 정리 넌적스 특성 정리
    <> 와 닫는 태그 없음 HTML그대로 차용해서 있음
    #login-button
    p.hidden.full
    class는 #으로, id 는 . 으로, div태그는 생략 가능
    텍스트는 한칸 띄우고 입력
    | (파이프)로 여러줄 입력
    style, javascript등 입력시 . 기호 사용
    - const node = 'Node.js'처럼 -로 선언 가능
    태그 = 속성으로도 사용 가능
    이스케이프 사용 X시에는 != 사용
    변수를 {{  }}로 감쌈
    {% set 변수 = '값' %} 형태로 내부 변수 사용가능
    이스케이프시 {{ 변수 | safe }} 사용
    반복 가능한 변수인 경우 each 사용 특수구문은 { % %} 안에 사용, 반복문도 여기에 넣는다. for in과 endfor사이에! 
    loop, index라는 특수변수 사용 가능
    if, else, case, else if 등의 조건문 사용가능 if, elif, else, endif 등을 {% %} 사이에 넣고 사용
    case문은 사용 X
    include 파일이름 형태로 다른 퍼그나 html 넣기 가능 {% include "파일경로" %} 형태로 사용가능
    extends로 레이아웃 불러오기 가능(공통 마크업) { % extends 경로 %} 형태로 사용가능

     

    ✅ 에러 처리 미들웨어

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

    localhost:3000/abc에 접속하면 아래처럼 확인이 가능하다. 

    에러 코드 아래부분에 스택 트레이스가 나오기 때문에, 서버 폴더 구조 유추가 가능하다. 따라서 보안상의 이유로, 배포환경에서는 숨겨진다. 

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

    Node.js 프로젝트 리팩토링(1)  (0) 2022.02.27
    Node.js 스터디 6주차  (0) 2022.02.22
    Node.js 스터디 4주차  (0) 2022.02.07
    Node.js 스터디 3주차  (0) 2022.02.05
    Node.js 스터디 2주차  (0) 2022.01.25
Designed by Tistory.