Express


Node.js 웹 프레임워크

🍡 Middleware

  • 서로 다른 어플리케이션이 통신을 하는데 사용되는 소프트웨어
  • API와 비슷한 역할을 한다.
  • Express는 백엔드 서비스 구성을 위한 다양한 상황에 맞는 여러가지 서비스를 미들웨어 형태로 제공한다.
  • 즉, Express에서의 미들웨어는 서버에 요청이 들어와서 응답으로 나갈 때까지 거치는 모든 함수 또는 기능을 의미한다고 생각하면 된다.
  • use()

  • 이를 이용해 미들웨어를 사용할 수 있다.
  • use 외의 방법은 각 method를 사용하는 것이다.
  • app.use(’요청 주소’, (req, res, next) ⇒ {}); 의 형태로 사용
  • 요청 주소는 ‘/’만 입력시 http://localhost:4000/ 을 의미
  • 요청 주소가 ‘/api’ ⇒ http://localhost:4000/api
  • const express = require('express');
    const server = express();
    const PORT = 4000;
    // http://localhost:4000/
    server.use('/', (req, res, next) => {
    res.send('Hello!');
    });
    server.listen(PORT, () => {
    console.log(`익스프레스 서버가 ${PORT}번 포트에서 작동 중입니다.`);
    });
    -----------------------------------------------------
    // 프론트 //
    // Hello!
    // 백 //
    // 익스프레스 서버가 4000번 포트에서 작동 중입니다.

    next()

  • next()는 callback 함수로서 해당 함수가 호출되면 현재 미들웨어를 종료하고 다음 미들웨어를 실행시킨다.
  • req 객체에 필드를 추가해서 데이터를 전달할 수 있다.
  • 정석적인 방법은 아니다.
  • const express = require('express');
    const server = express();
    const PORT = 4000;
    // http://localhost:4000/
    server.use('/', (req, res, next) => {
    console.log('미들웨어 1');
    req.reqTime = new Date();
    next();
    });
    server.use((req, res, next) => {
    console.log('미들웨어 2');
    res.send(`요청 시간은 ${req.reqTime} 입니다.`);
    });
    server.listen(PORT, () => {
    console.log(`익스프레스 서버가 ${PORT}번 포트에서 작동 중입니다.`);
    });
    -----------------------------------------------------
    // 프론트 //
    // 요청 시간은 Wed Mar 08 2023 14:46:54 GMT+0900 (대한민국 표준시) 입니다.
    // 백 //
    // 익스프레스 서버가 4000번 포트에서 작동 중입니다.
    // 미들웨어 1
    // 미들웨어 2
  • res.send()는 프론트에 응답을 보내고 console.log()는 백엔드에 콘솔을 찍는것이다.
  • Promise 사용하기

    const fs = require('fs').promises;
    const express = require('express');
    const server = express();
    const PORT = 4000;
    // http://localhost:4000/
    server.use('/', async (req, res, next) => {
    console.log('미들웨어 1');
    req.reqTime = new Date();
    req.fileContent = await fs.readFile('../package.json', 'utf-8');
    next();
    });
    server.use((req, res, next) => {
    console.log('미들웨어 2');
    console.log(
    `요청 시간은 ${req.reqTime} 입니다.`,
    `pakage.json의 내용을 보여드릴게요! \n ${req.fileContent}`
    );
    res.send(req.fileContent);
    });
    server.listen(PORT, () => {
    console.log(`익스프레스 서버가 ${PORT}번 포트에서 작동 중입니다.`);
    });
    -----------------------------------------------------
    // 프론트 //
    // (package.json의 내용)
    // 백 //
    // 미들웨어 1
    // 미들웨어 2
    // 요청 시간은 Wed Mar 08 2023 14:46:54 GMT+0900 (대한민국 표준시) 입니다. pakage.json의 내용을 보여드릴게요!
    // (package.json의 내용)

    ☁️ CRUD

    요청 메소드

  • Create → POST
  • Read → GET
  • Update → PUT
  • Delete → DELETE
  • HTTP Status

  • 100 번대 : 정보 / 리퀘스트를 받고 처리 중
  • 200 번대 : 성공 / 리퀘스트를 정상 처리
  • 300 번대 : 리디렉션 / 처리 완료를 위해서는 추가 동작 필요
  • 400 번대 : 클라이언트 에러 / 클라이언트에서 요청을 잘못 보냄
  • 500 번대 : 서버 에러 / 리퀘스트는 잘 들어 갔지만 서버에서 처리를 못함
  • 메소드 사용하기

    // @ts-check
    const express = require('express');
    const app = express();
    const PORT = 4000;
    app.get('/', (req, res) => {
    res.send('GET 메소드');
    });
    app.post('/', (req, res) => {
    res.send('POST 메소드');
    });
    app.put('/', (req, res) => {
    res.send('PUT 메소드');
    });
    app.delete('/', (req, res) => {
    res.send('DELETE 메소드');
    });
    app.listen(PORT, () => {
    console.log(`서버가 ${PORT}번에서 실행 중입니다.`);
    });
  • 위처럼 use 대신 각 method를 사용하여 미들웨어를 구현할 수 있다.
  • 이 경우 (포스트맨에서) 요청을 보낼 때 선택한 메소드에 따라 응답이 달라지게 된다.
  • 💫 URL로 Data를 받는 방법

    주소 값에 프론트에서 받아야 하는 정보를 담아서 받는 방법

    Req.params

  • 받을 URL에서 :파라미터명을 미리 정의해 두면 해당 내용이 req.params에 담겨서 전달된다.
  • /id/:id 로 하는 이유는 만약 다른 주소를 가진 미들웨어가 있을 경우에 구분하기 위해서이다.
  • const express = require('express');
    const app = express();
    const PORT = 4000;
    // http://localhost:4000/(id)
    app.get('/id/:id/name/:name/gender/:gender', (req, res) => {
    console.log(req.params);
    res.send(req.params);
    });
    app.listen(PORT, () => {
    console.log(`${PORT}번에서 서버 실행 중 . . .`);
    });
    -----------------------------------------------------
    // 요청 URL //
    localhost:4000/id/j56237/name/ke/gender/male
    // 프론트 //
    {
    "id": "j56237",
    "name": "ke",
    "gender": "male"
    }
    // 백 //
    {
    "id": "j56237",
    "name": "ke",
    "gender": "male"
    }

    Req.query

  • Params의 약점은 정의된 형태로만 데이터를 받을 수 있다는 점이다.
  • 이러한 형태 없이 보내고 싶다면 query를 사용하면 된다.
  • query의 url은 다음과 같다.
  • const express = require('express');
    const app = express();
    const PORT = 4000;
    // http://localhost:4000/(id)
    app.get('/', (req, res) => {
    console.log(req.query);
    res.send(req.query);
    });
    app.listen(PORT, () => {
    console.log(`${PORT}번에서 서버 실행 중 . . .`);
    });
    -----------------------------------------------------
    // 요청 URL //
    localhost:4000?id=ke&gender=male
    // 프론트 //
    {
    "id": "ke",
    "gender": "male"
    }
    // 백 //
    {
    "id": "ke",
    "gender": "male"
    }

    💨 Router

  • 프론트에서 백엔드로 요청을 보낼 때는 주소 값을 다르게 보내서 요청을 한다.
  • 백엔드에서는 각각의 주소에 따라 다른 역할을 해주면 된다.
  • 주소에 따라 각기 다른 역할을 하도록 나누는 방법을 Routing이라고 한다.
  • Express에는 Routing을 위한 미들웨어도 존재한다.
  • Router()를 사용하면 특정 url 요청에 대한 것들을 묶어서 처리가 가능하다.
  • // routes/users.js
    const express = require('express');
    const app = express();
    const userRouter = express.Router();
    const PORT = 4000;
    app.use('/users', userRouter);
    app.get('/', (req, res) => {
    res.send('Hello, Express World!');
    });
    // http://localhost:4000/users
    userRouter.get('/', (req, res) => {
    res.send('회원 목록');
    });
    userRouter.get('/id/:id', (req, res) => {
    res.send('특정 회원 정보');
    });
    userRouter.post('/add', (req, res) => {
    res.send('회원 등록');
    });
    app.listen(PORT, () => {
    console.log(`${PORT}번에서 실행 중 . . .`);
    });
  • 예를 들어, 위 코드에서는 회원 정보에 관한 요청은 전부 /users 이하의 요청으로 받는다.
  • 위 코드에서는 회원 id를 받으려면 /users/id/(id 이름) 에 GET 방식으로 요청을 하고, 회원 등록을 하려면 /users/add 에 POST 방식으로 요청한다.
  • 실습 - 회원정보 수정 API 만들기

    // @ts-check
    const express = require('express');
    const app = express();
    const userRouter = express.Router();
    const PORT = 4000;
    const USER = {
    1: {
    id: 'jke123',
    name: '장경은',
    },
    };
    app.use('/users', userRouter);
    app.get('/', (req, res) => {
    res.send('Hello, Express World!');
    });
    // http://localhost:4000/users
    userRouter.get('/', (req, res) => {
    res.send(USER);
    });
    userRouter.get('/id/:id', (req, res) => {
    const userData = USER[req.params.id];
    if (userData) {
    res.send(userData);
    } else {
    res.send('Cannot Found');
    }
    });
    userRouter.post('/add', (req, res) => {
    // query에 id와 name이 존재할 때만
    if (req.query.id && req.query.name) {
    const newUser = {
    id: req.query.id,
    name: req.query.name,
    };
    // USER 객체의 현재 길이 +1을 새로운 아이디로 함
    USER[Object.keys(USER).length + 1] = newUser;
    res.send('회원 등록 완료!');
    } else {
    res.send('요청이 잘못되었습니다.');
    }
    });
    userRouter.put('/modify/:id', (req, res) => {
    const num = req.params.id;
    // USER 객체에 키가 num인 값이 존재하지 않으면
    if (!USER[num]) {
    return res.send('요청하신 ID가 없습니다.');
    }
    // query에 id와 name이 존재할 때만
    if (req.query.id && req.query.name) {
    const modifiedUser = {
    id: req.query.id,
    name: req.query.name,
    };
    USER[num] = modifiedUser;
    res.send('회원 수정 완료!');
    } else {
    res.send('요청이 잘못되었습니다.');
    }
    });
    userRouter.delete('/delete/:id', (req, res) => {
    const num = req.params.id;
    // USER 객체에 키가 num인 값이 존재하면
    if (USER[num]) {
    delete USER[num];
    res.send('회원 삭제 완료!');
    } else {
    res.send('요청하신 ID가 없습니다.');
    }
    });
    app.listen(PORT, () => {
    console.log(`${PORT}번에서 실행 중 . . .`);
    });

    🦴 EJS

  • View Engine은 서버에서 처리한 데이터를 HTML 파일에 보다 편리하게 출력하게 하는 도구이다.
  • ejs는 View Engine 중 하나이다.
  • 예를 들어, userArr의 정보를 전달하려면 다음과 같다.
  • // @ts-check
    const express = require('express');
    const app = express();
    const userRouter = express.Router();
    const PORT = 4000;
    const userArr = [
    {
    id: 'jke123',
    name: '장경은',
    },
    {
    id: 'pororo',
    name: '뽀로로',
    },
    ];
    userRouter.get('/ejs', (req, res) => {
    const userCounts = userArr.length;
    res.render('index', { userArr, userCounts });
    });
  • 이렇게 써준 후 views 폴더를 만들고 그 안에 ejs 파일을 생성한다.
  • // index.ejs
    <body>
    <h1>Hello, EJS world!</h1>
    <h1>회원 목록</h1>
    <h2>총 회원 수 <%= userCounts %></h2>
    <ul>
    <li>
    <p>ID : <%= userArr[0].id %></p>
    <p>NAME : <%= userArr[0].name %></p>
    </li>
    </ul>
    </body>
  • <%= %> 안에 자바스크립트를 작성하면 인식한다. =은 변수를 사용하겠다는 의미이다.
  • 따라서 for문 같은 걸 사용할 땐 =은 사용하지 않아도 된다.
  • // index.ejs
    <body>
    <h1>Hello, EJS world!</h1>
    <h1>회원 목록</h1>
    <h2>총 회원 수 <%= userCounts %></h2>
    <ul>
    <% if(userCounts > 0) { %>
    <% for(let i = 0; i < userCounts; i++) { %>
    <li>
    <p>ID : <%= userArr[i].id %></p>
    <p>NAME : <%= userArr[i].name %></p>
    </li>
    <% } %>
    <% } else { %>
    <li>회원 정보가 없습니다!</li>
    <% } %>
    </ul>
    </body>

    ⚠️ Error 핸들링

  • Error 던지기
  • const err = new Error('해당 ID를 가진 회원이 존재하지 않습니다!');
    err.statusCode = 404;
    throw err;
  • err 객체를 만들어서 throw로 전달한다.
  • 해당 에러를 App.js에서 미들웨어를 만들어 핸들링한다.
  • app.use((err, req, res, next) => {
    console.log(err.stack);
    res.status(err.statusCode);
    res.send(err.message + `<a href="/">홈으로</a>`);
    });
  • 이렇게 되면 브라우저(사용자)에는 a태그와 함께 깔끔한 메세지를 띄운다.
  • 콘솔(개발자)에는 에러의 stack을 띄운다.
  • ⌨️ form으로 데이터 받아오기

  • ejs에 form태그 추가하기
  • action 속성은 보내고자 하는 주소 값
  • method 속성은 보내는 method 설정
  • <form action="/users/add" method="POST">
    <div>
    <label>아이디</label>
    <input type="text" name="id" />
    </div>
    <div>
    <label>이름</label>
    <input type="text" name="name" />
    </div>
    <div>
    <label>이메일</label>
    <input type="text" name="email" />
    </div>
    <button type="submit">제출</button>
    </form>
  • form에 입력된 내용을 받아오기 위해서 body-parser 라는 모듈을 사용한다.
  • const bodyParser = require('body-parser');
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
  • 이렇게 해주면 body-parser는 form에서 전송된 정보를 req.body에 담에서 obj로 전달해준다.
  • express 4.16 이상에서는 body-parser 대신 express로도 가능하다.
  • router.post('/add', (req, res) => {
    if (Object.keys(req.query).length >= 1) {
    if (req.query.id && req.query.name && req.query.email) {
    const newUser = {
    id: req.query.id,
    name: req.query.name,
    email: req.query.email,
    };
    USER.push(newUser);
    res.send('회원 등록 완료!');
    } else {
    const err = new Error('ID, Name, Email을 모두 입력하여야 합니다.');
    err.statusCode = 400;
    throw err;
    }
    } else if (req.body) {
    if (req.body.id && req.body.name && req.body.email) {
    const newUser = {
    id: req.body.id,
    name: req.body.name,
    email: req.body.email,
    };
    USER.push(newUser);
    res.redirect('/users');
    } else {
    const err = new Error('ID, Name, Email을 모두 입력하여야 합니다.');
    err.statusCode = 400;
    throw err;
    }
    } else {
    const err = new Error('ID, Name, Email을 모두 입력하여야 합니다.');
    err.statusCode = 400;
    throw err;
    }
    });
  • 위처럼 작성하면 query로 들어온 것과 form으로 들어온 것 모두를 처리할 수 있다.
  • form 외에 백엔드로 데이터를 보내는 방법에는 XMLHttpRequest, JQuery, Fetch() 등이 있다.
  • 🥛 DB와 연동된 게시판 만들기

    Controllers

  • mysql을 패키지로 설치해주고 다음 코드로 연결해준다.
  • // dbConnect.js //
    const mysql = require('mysql');
    const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '12341234',
    port: '3306',
    database: 'mydb',
    });
    connection.connect();
    module.exports = connection;
  • mysql과 연결된 것을 이용하여 query를 통해 SELECT문을 보내 DB를 받아온다.
  • 게시글을 작성할 때에도 query문을 이용해 작성해준다.
  • // boardController.js //
    const connection = require('./dbConnect');
    const boardDB = {
    // 모든 게시글 가져오기
    getAllArticles: (cb) => {
    connection.query('SELECT * FROM mydb.board', (err, data) => {
    if (err) throw err;
    cb(data);
    });
    },
    // 게시글 추가하기
    wrtieArticle: (newArticle, cb) => {
    connection.query(
    `INSERT INTO mydb.board (TITLE, CONTENT) VALUES ('${newArticle.title}', '${newArticle.content}');`,
    (err, data) => {
    if (err) throw err;
    cb(data);
    },
    );
    },
    // 특정 ID 값을 가지는 게시글 찾기
    getArticle: (id, cb) => {
    connection.query(
    `SELECT * FROM mydb.board WHERE ID_PK = ${id};`,
    (err, data) => {
    if (err) throw err;
    cb(data);
    },
    );
    },
    // 글 수정 DB에 반영하기
    modifyArticle: (id, modifyArticle, cb) => {
    connection.query(
    `UPDATE mydb.board SET TITLE = '${modifyArticle.title}', CONTENT = '${modifyArticle.content}' WHERE ID_PK = ${id};`,
    (err, data) => {
    if (err) throw err;
    cb(data);
    },
    );
    },
    // 글 삭제하기
    removeArticle: (id, cb) => {
    connection.query(
    `DELETE FROM mydb.board WHERE ID_PK = ${id}`,
    (err, data) => {
    if (err) throw err;
    cb(data);
    },
    );
    },
    };
    module.exports = boardDB;

    Routes

    // dbBoard.js //
    const express = require('express');
    const boardDB = require('../controllers/boardController');
    const router = express.Router();
    // 게시판 페이지 호출
    router.get('/', (req, res) => {
    boardDB.getAllArticles((data) => {
    const ARTICLE = data;
    const articleCounts = ARTICLE.length;
    res.render('db_board', { ARTICLE, articleCounts });
    });
    });
    // 글 쓰기 페이지 모드
    router.get('/write', (req, res) => {
    res.render('db_board_write');
    });
    // 데이터베이스에 글 추가
    router.post('/write', (req, res) => {
    if (req.body.title && req.body.content) {
    boardDB.wrtieArticle(req.body, (data) => {
    // affectedRows를 이용하여 DB에 잘 입력되었는지 확인
    if (data.affectedRows >= 1) {
    res.redirect('/dbBoard');
    } else {
    const err = new Error('글 쓰기 실패');
    err.statusCode = 500;
    throw err;
    }
    });
    } else {
    const err = new Error('글 제목이나 내용이 없습니다.');
    err.statusCode = 400;
    throw err;
    }
    });
    // 글 수정 모드로 이동
    router.get('/modify/:id', (req, res) => {
    boardDB.getArticle(req.params.id, (data) => {
    if (data.length > 0) {
    res.render('db_board_modify', { selectedArticle: data[0] });
    } else {
    const err = new Error('해당 ID 값을 가지는 게시글이 없습니다.');
    err.statusCode = 500;
    throw err;
    }
    });
    });
    // 글 수정하기
    router.post('/modify/:id', (req, res) => {
    if (req.body.title && req.body.content) {
    boardDB.modifyArticle(req.params.id, req.body, (data) => {
    if (data.affectedRows >= 1) {
    res.redirect('/dbBoard');
    } else {
    const err = new Error('글 수정 실패');
    err.statusCode = 500;
    throw err;
    }
    });
    } else {
    const err = new Error('글 제목이나 내용이 없습니다.');
    err.statusCode = 400;
    throw err;
    }
    });
    // 글 삭제하기
    router.delete('/delete/:id', (req, res) => {
    boardDB.removeArticle(req.params.id, (data) => {
    if (data.affectedRows >= 1) {
    res.status(200).send('삭제 성공');
    } else {
    const err = new Error('삭제 실패');
    err.statusCode = 500;
    throw err;
    }
    });
    });
    router.get('/getAll', (req, res) => {
    boardDB.getAllArticles((data) => {
    res.send(data);
    });
    });
    module.exports = router;

    🍘 HTTP Session

  • 브라우저가 아닌 서버에 저장되는 쿠키이다.