sec03. Express로 서버를 만들어보자

sngmin·2024년 1월 16일

01. Express

'Node.js를 위한 빠르고 개방적인 간결한 웹 프레임워크'

  • http
  • Node.js
  • 라이브러리

🗣️ Express 널리, 오래 쓰임 -> 사용자가 많고 새로운 기술 많이 올라온다. 가장 기본적인 프레임워크

yarn add express

express는 개발환경에서만 사용되는 것이 아니라 실제로 코드를 올릴 때도 포함되어야 함. -D 없이 add

02. REST API

서버가 작동하는 방식에 대해 먼저 알아보자


서버와 클라이언트 서로 소통하면서 요청/응답 주고 받음 -> 요청과 응답은 어떤 방식에 의한 것인가?
➡️ HTTP Protocol: 서버와 클라이언트를 이어주는 길!
서버와 클라이언트는 HTTP Protocol을 통해서 소통을 함


[HTTP 구조]

start line - 시작 문장
headers - 요청과 응답의 추가 정보
body - 중요한 정보

REST API - 서버와 클라이언트가 정보를 주고받는 방식에 대한 약속
REST = 자원 + 행위 + 표현

자원: HTTP URL, 즉 주소. 서버에 요청을 보내는 주소.

protocol: http라는 프로토콜 이용
host: 주인이 localhost, 컴퓨터가 주인 (naver.com인 경우 naver.com이 host)
port: 요청이 들어오는 입구 (8000 - 로컬에서 서버 테스트 할 때, 443 - https에 사용되는 포트 443은 생략 가능)
path: / 경로. 경로 안에 응답이나 데이터 존재한다고 볼 수 있음
path variable: 어떤 값이든 올 수 있음
query: 추가적인 정보 담음, 시작은 물음표로 구분은 & 표시로. key와 value를 ?, =, & 이용해서 구분지음

행위: 요청을 하는 방법
HTTP Method- GET, POST, PATCH, PUT, DELETE
같은 end point 가지더라도 메서드가 다르면 다른 요청이 됨, 하나의 end point당 다섯가지의 요청 존재

CRUD - Create(POST 통해서 데이터 생성), Read(GET으로 어떤 정보 불러옴), Update(PATCH, PUT PUT은 데이터의 전체, PATCH는 데이터의 일부 수정), Delete(DELETE)

표현: 요청과 응답의 형식, 요청과 응답을 어떻게 할 것인가?
JSON, XML, TEXT
🗣️ 앞으로 다룰 대부분의 자료는 JSON

💡 간단한 규칙들!

  • 소문자 사용
  • 언더바(_) 대신 하이픈(-) 사용
  • 마지막에 슬래시(/) 포함 X
  • 행위 포함 X (URL에 동사 넣지 X)
  • 파일 확장자 URL에 포함 X
  • 영어는 복수형으로 사용

📖 REST API 및 HTTP에 대한 추가 조사하기

03. Middleware 구성 및 이해


client가 요청 보낼 때 항상 middleware 거쳐 오도록 장치 설치해놓은 것
공통적인 처리 과정 필요할 때 사용
ex. middleware를 통해 디도스 공격시 서버 다운되는 것 피하기 위해 정해진 ip의 클라이언트만 접근 가능하도록 함

middleware 어디에서 중간 다리 역할을 하느냐에 따라 두 가지로 구분

어플리케이션 레벨 미들웨어 - 클라이언트의 모든 요청이 거쳐감

라우터 레벨 미들웨어 - 모든 요청에 대해 검사하지는 않음 어떤 한 요청에 대해서만. 메서드(라우터)마다 middleware 다르게 적용

04. Express 기본 모듈 및 자주 사용되는 라이브러리

📌 가장 많이 사용되는 라이브러리

cors - 접근 가능한 도메인을 제한
helmet - http 헤더 설정을 통해 보안 강화
dayjs - 날짜 관련 작업을 할 때 사용
nodemon - 수정된 코드에 따라 다시 서버를 실행
axios - 다른 서버(ex. 카카오, 네이버 지도 등등)에 요청을 보낼 때 사용
bcrypt - 비밀번호 암호화에 사용(단방향 암호화. 복호화가 불가능)
jsonwebtoken - 로그인한 유저 정보를 토큰으로 만들기 위해 사용

yarn add cors helmet dayjs
yarn add -D nodemon

cors와 helmet 사용
cors와 helmet은 middleware에 넣음

import cors from "cors";
import helmet from "helmet";
app.use(cors({
    origin : "https://www.naver.com"
}));
// https://www.naver.com 에서 요청이 와야만 서버가 응답 보내주도록 도메인 제한

app.use(cors()); // 모든 도메인 접근 허용
app.use(helmet());

dayjs 사용

import dayjs from "dayjs";
const today = new Date(); // 날짜 객체 생성
const todayTodayjs = dayjs(today).format("YYYY-MM-DD"); // dayjs로 변환한 날짜 출력, 출력 형태 지정


터미널에 yarn dev 입력하니 "listen EADDRINUSE: address already in use :::8000" 에러가 발생했다ㅠ.ㅠ

8000번 포트 사용하는 프로세스 찾아서 강제종료를 해야 했다

lsof 명령어로 프로세스 확인, -i 옵션 사용하면 특정 포트 사용중인 프로세스만을 볼 수 있다.
프로세스 확인 후 $ kill -9 (해당 포트를 사용하는 PID값)으로 프로세스 강제종료!

[8000번 포트를 정상적으로 이용할 수 있게 된 모습]

nodemon 사용
script로 사용

--exec: 실행

코드 수정하고 저장 하면 자동으로 서버가 재실행 됨

bcrypt jsonwebtoken 사용

yarn add bcrypt jsonwebtoken
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
const password = "1234";
const hashedPassword = bcrypt.hashSync(password, 10);
// 암호화된 패스워드 만듦

console.log({hashedPassword});

const token = jwt.sign("1234", "a123afjafoawndcwafeo");
console.log({token});

📖 강의에서 다룬 라이브러리 설치 및 cors, helmet, nodemon 적용하고 github에 업로드하기

05. 간단한 REST API 작성하기

express 앱 생성하기

// yarn add express
import express from "express";

const app = express();

Application Level 미들웨어 작성하기

// 요청을 json 형태로 받는데 특화된 middleware
app.use(express.urlencoded({ extended: true, limit: "700mb" }));
app.use(express.json()); 

app.use(cors({ origin: "*"}));
app.use(helmet());

app.use

미들웨어 - app.use에 바로 middleware 함수 넣어주면 미들웨어로 동작
라우터 - 경로를 입력해 준 후 라우터를 넣어줘야 함

app.-HTTP 메서드

app.get 함수 사용하면 GET 메서드로 들어온 요청 받을 수 있는 것

express 앱 실행하기

app.listen(8000, () => {
	console.log("Server is running on port 8000");
});
app.use(express.json());
app.use(express.urlencoded({extended: true, limit:"700mb"}));

🗣️ app.use는 미들웨어, 라우터를 위한 함수이기 때문에 미들웨어와 라우터에만 사용. use에서 사용하는 endpoint들은 다 get으로 통일되기 때문에 어울리지 않음.

HTTP Method 통해서 작성하는 REST API

// GET Method 통해서 작성하는 REST API
// 유저 정보 가져오기
// query or Path로 요청 받음(body는 손실되는 경우가 있다)
// status는 응답의 성공 상태를 의미
// get의 경우에는 성공하면 status 보통 200 가져옴
app.get("/users", (req, res) => {
    res.status(200).json({users}); 
});

// POST Method
// 유저 생성
// 요청 body로 많이 받음
// 성공 status: 201
app.post("/users", (req, res) => {
    const {name, age} = req.body;

    users.push({
        id: new Date().getTime(),
        name,
        age
    });
    
    res.status(201).json({users});
});

// PATCH Method
// 유저 수정
// 성공 status: 204
// req.params.id
app.patch("/users/:id", (req, res) => {
    const{id} = req.params;
    const{ name, age} = req.body;
    console.log("param", req.params);
    const targetUserIdx = users.findIndex((user) => user.id === Number(id));

    users(targetUserIdx) = {
        id : users[targetUserIdx].id,
        name : name ? name : users[targetUserIdx].name,
        age: age ?? users[targetUserIdx].age
    }
});

// DELETE Method
// 유저 삭제
// 성공 status: 204
// 경로에 path variable 추가
app.delete("/users/:id", (req, res) => {
    const {id} = req.params;

    const deletedUsers = users.filter((user) => user.id != Number(id));
    users = deletedUsers

    res.status(204).json();
});

"/users": endpoint. 각각 들어온 메서드에 따라 가는 곳이 달라짐

📖 app.use를 통해 간단한 미들웨어 구현 후 app.get, app.post, app.patch, app.delete 통해 REST API 만들기

06 라우터 분리하기 - 1 ⭐️


라우터는 클라이언트에게 받은 요청의 경로 지정해줄 수 있음

라우터 생성하기

import { Router } from "express";

const router = Router();

라우팅 경로와 함수 등록

const router = Router();

router.get("/", (req, res) => {});
router.get("/detail", (req, res) => {});
router.post("/", (req, res) => {});
router.patch("/", (req, res) => {});
router.delete("/", (req, res) => {});

export default router;

라우터 등록하기

import express from "express";
import UserRouter from "./users"; // 라우팅 경로와 함수 등록한 파일명이 users일 때

const app = express();

/**
* application middleware
*/

const UserRouter = Router();

// UserRouter endpoint들 등록
UserRouter.get("/", (req, res) => {}); // 슬래시 뒤에 아무것도 없으면 기본적으로 설정한 경로
UserRouter.get("/detail", (req, res) => {});
UserRouter.post("/", (req, res) => {});
UserRouter.patch("/", (req, res) => {});
UserRouter.delete("/", (req, res) => {}); 

app.use("/users", UserRouter); // users에 해당하는 경로 등록

let users = [
    {
        id: 1,
        name: "sngmin",
        age: 12,
    },
];

...

const UserRouter = Router();

// GET /users
// 전체 유저 조회
UserRouter.get("/", (req, res)=> {
    res.status(200).json({users});
});

// GET /users/detail/:id
// 유저 한 명을 불러오는 API
UserRouter.get("/detail/:id", (req, res)=> {
    const { id } = req.params;

    users.find((user) => user.id === Number(id));

    res.status(200).json({users});
});

// POST /users/
// 유저를 생성하는 API
UserRouter.post("/", (req, res)=> {
    const {name, age} = req.body;
    user.push({
        id: new Date().getTime(),
        name,
        age,
    });

    res.status(201).json({users});
});

// 유저 라우터 등록
app.use("/users", UserRouter);

서버 실행

Postman으로 확인하기

🚨 새로운 body를 작성하고 요청을 보내도 업데이트가 되질 않음... ㅠㅠ 뭐가 문제일까

라우터 사용하는 이유

  • 코드 분리할 수 있고
  • endpoint마다 관리할 수 있기 때문

=> 위처럼 사용하면 라우터 사용하는 이유의 장점을 살릴 수가 없음

💡 src>users>index.js
유저 선언 부분 옮겨졌고 모든 라우터가 분리됨.(적용하는 부분과 내용을 적는 부분 분리, 오류 났을 때 수정이 더 쉽다)

import { Router } from "express";

// Router
class UserController{
    router;
    users = [
        {
            id: 1,
            name: "sngmin",
            age: 12,
        },
    ];
    constructor(){
        this.router = Router();
        this.init();
    }

    init(){
        this.router.get("/", this.getUsers.bind(this));
        this.router.get("/detail/:id", this.getUser.bind(this));
        this.router.post("/", this.createUser.bind(this));
    }

    getUsers(req, res){
        res.status(200).json({users: this.users});
    }
    
    getUser(req, res){
        const {id} = req.params;

        const user = this.user.find((user) => user.id === Number(id));

        res.status(200).json({user});
    }

    createUser(req, res) {
        const { name, age } = req.body;

        this.users.push({
            id: new Date().getTime(),
            name,
            age,
        });

        res.status(201).json({users: this.users});
    }
}

const userController = new UserController();

export default userController;

src>index.js

import express, { Router } from "express";
import cors from "cors";
import helmet from "helmet";
import dayjs from "dayjs";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import UserController from "./users";

const app = express();

// 미들웨어
app.use(cors());
app.use(helmet());
app.use(express.json());
app.use(express.urlencoded({extended: true, limit:"700mb"}));

app.use("/users", UserController.router);

// req: 요청
// res: 응답
app.get("/", (req, res) => {
    res.send("Nodejs 강의 재밌어요!");
});

app.listen(8000, () => {
    console.log("서버가 시작되었습니다.");
});

🚨 서버 실행하면
Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/Users/sngmin/Desktop/LECTURE/express-practice/src/users' is not supported resolving ES modules imported from /Users/sngmin/Desktop/LECTURE/express-practice/src/index.js 오류 뜸

왜지.............

07. 에러 헨들링하기

0개의 댓글