express

Zain·2023년 4월 26일
0

정리가 필요한 게시글

바벨의 사용법

바벨을 설치한다
npm install -D @babel/core @babel/cli @babel/node @babel/preset-env
-D의 경우에는 개발할때만 사용하고 배포시에는 미포함하기 위해(성능저하)

  "babel": {
    "presets" : [
      "@babel/preset-env"
    ]
  }

//바벨을 세팅

 "scripts": {
    "dev":"babel-node src/index.js"
  },

바벨과 함께 실행시키기 위한 명령어 메인 파일의 경로를 적어준다

middleware에 대해서
클라이언트의 요청이 서버로 전달되기 이전 중간에 위치,
공통적인 요청을 해결하거나,

두종류가 있음
어플리케이션 레벨 미들웨어
클라이언트에서 보내는 모든 요청은 미들웨어를 거쳐가는 형태
라우터 레벨 미들웨어
모든요청에 대해서 검사하지 않음,
특정한 요청이 오면 그에 해당되는 미들웨어를 거치는 형태

실제 프로젝트에서
어플리케이션 미들웨어는 프로젝트를 설계할때 이루어지며 엄청 복잡한 로직을 다루지는 않음,

express에서 자주 사용되는 모듈 및 라이브러리
cors 접근 가능한 도메인을 제한
helmet http헤더 설정을 통해 보안 강화
dayjs 날짜 관련 작업을 할 때 사용
nodemon 수정된 코드에 따라서 다시 서버를 실행

import express from "express";
//cors와 helmet은 미들웨어에서 사용
import cors from "cors"
import helmet from "helmet"
import dayjs from "dayjs"

express에서 미들웨어 사용하기/ cors를 사용해보기
// 모든 요청에 응답할때는 cors() 또는 cors({origin: "*"})
app.use(cors())

  • DAYJS사용하기
import dayjs from "dayjs"
const today = new Date()
const todayToDayjs = dayjs(today).format("YYYY-MM-DD")
console.log(todayToDayjs)
//2023-02-25

-express에서 nodemon 적용하기
기존의
바벨을 적용시키기 위한 스크립트에서
"dev": "babel-node src/index.js"

"dev": "nodemon --exec babel-node src/index.js"
//nodemon --exec 를 추가해준다

-bcrypt 비밀번호 암호화 단방향 통신
-jsonwebtoken - 로그인한 유저 정보를 토큰으로 만들기 위해 사용
둘다 암호화를 시키고 토큰은 유효기간이 있음

  • restapi 작정하기
    미들웨어 사용방법
    app.use() 키워드를 사용
    위에 키워드는 어플리케이션 레벨 미들웨어임
    app.use()는 라우터를 등록할때도 사용됨
    app.use("/users", router)

app.use()함수안에 미들웨어 함수를 등록하면 미들웨어로 동작이 되지만
라우터로 동작을 시키고싶으면 경로, 라우터 를 입력해주면 된다.

--- 라우터 분리하기 중요

요청에대한 응답의 경로를 설정해준다

라우터를 사용하지 않고 app.get, app.post 등으로 할 수 있지만
그렇게 되면 규모가 커지면 index.js에서 모든 요청을 적어야 되기 때문에 복잡도가 올라간다

라우터를 생성하게 되면 해당되는 라우터만을 묶어서 따로 파일로 분리하고
메인 index.js에서 라우터를 사용할 수 있게끔 임포트를 해준다

//라우터 생성하기
import express, {Router} from "express";
// 라우터의 이름은 UserRouter
const UserRouter = Router() 

//app.get대신에 라우터이름.get형식으로 사용한다, /users
UserRouter.get("/", (req, res)=>{
    res.status(200).json(users)
})
// users/detail/:id
UserRouter.get("/detail/:id", (req, res)=>{
    const id = req.params.id
    const user = users.find((user)=> user.id === Number(id))
    res.status(200).json(user)
})
// /users
UserRouter.post("/", (req, res)=>{
    const {name, age} = req.body
    users.push({
        id : new Date().getTime(),
        name,
        age
    })
    res.status(201).json(users)
})
//최종적으로 app.use("라우터의 경로", 라우터명) 을 입력해주어 라우터를 사용할 수 있게 해준다,
app.use("/users", UserRouter)

라우터를 다른 파일로 분리하기

app.js파일에 라우터를 계속 적어주면 코드량도 길어지고 가독성이 떨어지게 된다

라우터 내용이 있는 파일,
라우터들을 한곳에 모아주는 파일,
최종적으로 index.js파일로 구성되어있다.

위와같은형태

// controllers > users > index.js
//라우터 모듈을 갖고옴
import { Router } from "express";
//라우터 데이터가 담긴 calss생성
class UserController{
    router;
    path = "/users"
    users = [
        {
            id:1,
            name:"조성홍",
            age:"23살"
        }
    ]
    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))
        this.router.delete("/detail/:id", this.removeUser.bind(this))
    }
    getUsers(req, res){
        res.status(200).json(this.users)
    }

    getUser(req, res){
        const {id} = req.params;
        const user = this.users.find((user)=> user.id === Number(id))
        res.status(200).json(user)
    }

    createUser(req, res){
        const data = req.body;
        this.users.push({
            id: new Date().getTime(),
            name: data.name,
            age: data.age
        })
        res.status(201).json(this.users)
    }

    removeUser(req, res){
        const data = req.params;
        const users = this.users.find((user)=>{
            user.id != data.id
        })
        res.status(200).json(users)
    }
}
//라우터를 userController변수에 담아주고
const userController = new UserController();
// export해준다
export default userController
// controllers > index.js
import UserController from "./users";
// 반복문을 사용하기 위해 배열형태로 export
export default [UserController]
// index.js
import express from "express";

import Controllers from "./controllers";


const app = express();

app.use(express.json());
app.use(express.urlencoded({extended:true, limit:"700mb"}));

//배열로 전달된 controllers의 데이터를 반복문을 사용해 app.use()로 라우터를 등록해준다.
Controllers.forEach((controller)=>{
    app.use(controller.path, controller.router)
});

app.listen(8001, ()=>{
    console.log("서버시작스")
})

== 에러를 핸들링 해보자

에러를 핸들링 할땐 try, catch문을 사용하여 할 수 있다.
try문에서 만약 user가 없으면 throw를 하게되고 이때 코드는 throw에서 멈추고 더이상 진행을 하지 않고 catch로 넘어가게 된다. catch(err)의 매개변수인 err은 throw의 객체를 전달받는다,

-next란
에러의 미들웨어 next의 err를 넣어주게 되면 미들웨어로 이동한다.

//에러 미들웨어의 형태

실제 적용 코드는

    getUser(req, res, next){
        try{
            const {id} = req.params;
            const user = this.users.find((user)=> user.id === Number(id))
            if(!user){
                throw {status:404, message:"유저가 없어용"}
            }
            res.status(200).json(user)
          //꼭 최종 성공 코드는 throw구문 이후에 사용해줘야된다
          // throw시 코트는 중지되고 catch로 넘어가기 때문
        }catch(err){
            next(err)
          //에러 미들웨어로 throw객체를 담고 넘어간다
        }
    }
//에러 미들웨어
// app.use에 바로 콜백함수를 넣어준다
app.use((err, req, res, next)=>{
    res.status(err.status || 500)
      .json({massage: err.message || "서버에러"})
})
//응답의 throw객체에 있는 status코드가 넘어온다.
// throw로 캐치한 에러면 해당코드를
// .json으로 변환한 메세지를 노출시킨다.

DTO
계층간(파일, 컨트롤러, 서비스)사이에 존재함
애초에 DTO를 사용하는 이유는 타입 유효성 검사 목적도 있지만 데이터를 주고 받을때 규약(설계도) 느낌으로 안전하게 필요한 데이터만 통신하기 위함입니다. 회원가입을 하는 도메인에서 필요한 데이터는 유저 이름, 유저 이메일, 유저 비밀번호가 있을 겁니다. 하지만 요청을 할때 필요없는 데이터를 보낸다던지, 누락된 데이터를 보낸다던지를 체크할 필요가 있습니다. 이때 DTO가 사용됩니다. 통신 데이터 규칙이라고 생각하면 좋을 것 같습니다.
dto/

prisma

sql언어 대신 자바스크립트 문법을 통해서 db와 통신
ORM이라고 함 prisma는 자바스크립트를 통해서 DB에 접근 시켜주는 도구임 오테이블 생성, crud

1. prisma 설치

설치

npm install -D prisma
yarn add -D prisma

설치후에 기본 작업

yarn prisma init

.env파일과 prisma/schema.prisma가 생성됨

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
// mysql로 변경해준다. db의 타입을 설정
provider = "mysql"

2. tabel 작성

  • @id: pk,
  • @default(uuid()):id값 자동생성
  • age : Int? : null
model User{
  id String @id @default(uuid())
  name String @db.VarChar(200)
  email String @db.VarChar(200)
  phoneNumber String @db.Char(11)
  age Int
  posts Post[]
}

model Post{
  id String @id @default(uuid())
  title String @db.VarChar(200)
  content String @db.LongText

  userId String  // fk
  //fields : 어떤걸 fk로 쓸고녀 , 
  //references: User테이블에서 어떤걸로 구분할거냐
  user User @relation(fields: [userId], references: [id])
}

3. db연동

// .env
// 초기값
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

DATABASE_URL="mysql://root:password@localhost:3306/lecture"

4. prisma 실행

  • db를 최신화 하거나, 수정사항이 있을때 아래 명령어를 사용한다.
    yarn prisma generate

  • 작성한 테이블을 db에 반영

  • 아래 명령어를 실행하면 db에 _prisma_migrations 테이블이 생성/수정됨

  • git처럼 db수정내역 관리 가능
    yarn prisma migrate dev

  • 실제 db에 변경 이력을 적용하지 않고 migration폴더 내부에 이력만 생성됨
    yarn prisma migrate dev --create-only

  • 다른사람이 만들어 놓은 데이터베이스를 prisma/schema.prisma 파일에 내용을 업로드함

  • 혹은 수정된 내용을 불러올때
    yarn prisma db pull

  • maigration 이력 없이 db에 바로 수정사항 반영
    yarn prisma db push

db테이블 작성 -> yarn prisma migrate dev -> yarn add @prisma/client -> yarn prisma generate

05 prisma query #1

기본적인 db crud 하기 !

prisma client 생성 및 prisma를 연동하기

// prisma client 생성
// src/database.js
import {PrismaClient} from "@prisma/client"

const prisma = new PrismaClient()

export default prisma

// prisma를 index.js에 연결
// src/index.js
//즉시실행 함수 
(async ()=>{
    const app = express();

    await database.$connect();

    app.use(express.json());
    app.use(express.urlencoded({extended:true, limit:"700mb"}));
    
    Controllers.forEach((controller)=>{
        app.use(controller.path, controller.router)
    });
    
    
    app.use((err, req, res, next)=>{
        res.status(err.status || 500).json({massage: err.message || "서버에러"})
    })
    
    app.listen(8001, ()=>{
        console.log("서버시작스")
    })
})();
// 기존의 로직을 즉시실행 함수에 넣어주고 db에 연결이 된 후 나머지 로직들이 작동되게끔 
// 연결해준다.

user 폴더 생성

테이블 단위로 폴더를 구성해주면 리펙토링이나 가독성면에서 효과적(개인취향)

  • users폴더(user테이블) 생성
  • controllers에 라우터 및 컨트롤러들을 모아놓았는데 users폴더 안에 controller 폴더를 만들어서 각각의 컨트롤러로 분리
  • controllers 폴더에서는 각각의 폴더에서 작성된 컨트롤러들을 모두 import해주고 export해주는 역할

database get
database.모델명(테이블명).findUnique()

  • 유니크한 값(pk값)을 통해 데이터를 찾음
  • 하나씩 값을 찾는 형식은 위와같은 함수로 진행함
const user = await database.user.findUnique({
	where:{
    	id: 1
    }
})
// select를 넣어주지 않으면 default로 * 로 됨
// user : {id: 1, name: "아무개", age:23 ,,,,,,,}

const user = await database.user.findUnique({
	where:{
    	id: 1
    },
  	select:{
    	id:true,
      	name:true
    }
})
// user : {id: 1, name: "아무개"} 없으면 user : null

database.모델명.findFirst()

  • 조건에 맞는 데이터중 가장 최근 데이터를 찾음
javascript
const user = await database.user.findFirst({
	where:{
    	name: "개구리"
    }
})
// 만약 "개구리"라는 이름의 데이터가 2개이상 존재할 때,
// 가장 최근에 생성된 "개구리"를 갖고온다.

database.모델명.findMany()

  • 조건에 맞는 데이터를 모두 불러옴
  • 배열을 반환
  • 가장 많이, 주로 사용하는 함수이다
const user = await database.user.findMany({
    //where 조건  이름이 개구리이고 나이가 23살인 데이터를 불러옴
	where:{
    	name: "개구리",
        age: 23
    }
 // user : [{id:**, name:**, age:** ,,}, {id:**, name:**, age:** ,,}, {id:**, name:**, age:** ,,}, {id:**, name:**, age:** ,,}] or user :[]

OR

const user = await database.user.findMany({
	where:{
		OR:[
          {
          	name : "bob"
          },
          {
          	age : 20
          }
        ]
    }
  // OR:[{조건1}, {조건2}]
  // name이 bob이거나 나이가 20인 모든 데이터를 불러옴 

orderBy

  • 해당 컬럼을 기준으로 desc(내림차순), asc(오름차순)으로 불러옴
const user = await database.user.findMany({
	where:{
		job: dev
    },
  	orderBy:{
    	age:"desc"
    }
// job이 dev인 데이터의 age가 내림차순으로 불러옴

skip / take

  • skip한 개수만큼 앞에서 제외하고 take만큼 가져오기

    skip2, take : 5
    위와 같은 조건일 경우 앞에 2개를 제외하고 3번째 부터 5개의 값을 가져온다.
const user = await database.user.findMany({
	skip : 2,
  	take : 5

create
데이터를 생성하기
database.tabel명.create()

const user = await database.user.create({
	data:{
    	age:20,
      	name : "bob"
    }
})
// 컬럼 값이 auto increment 일 경우 생략 

update
database.tabel명.update()

  • 대상을 하나만 지정해주어야됨
  • 조건을 pk값, 유니크한 값으로 조건을 정해주어야됨
  • data안에 수정할 컬럼과 값만 넣어주면 됨
const user = await database.user.update({
	where:{
    	id: 1
    },
  	data :{
    	name: "jason"
    }
 // user 테이블에서 id컬럼이 1인 값의 name컬럼의 값을 jason으로 update한다.
 // 컬럼이 id, name, age, job 등등이 있는 경우 변경을 원하는 컬럼만 넣어주면 됨 

delete
database.tabel명.delete()

  • 대상을 하나만 지정해주어야됨
const user = await database.user.delete({
	where:{
    	id: 1
    }
 // id가 1인 데이터를 삭제

db에서 데이터를 가져오기

현재까지 정리 !

현재까지의 파일 구조이다.

위에서 부터 정리해본다.

/prisma/schema.prisma

: yarn prisma init 을해주면 해당 폴더와 schema.prisma파일이 생성된다.
기본적인 prisma설정과 테이블을 작성할 수 있다.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  // db사용 언어
  provider = "mysql"
  url      = env("DATABASE_URL")
}
// db에 테이블 생성
model User{
  id String @id @default(uuid())
  name String @db.VarChar(200)
  email String @db.VarChar(200)
  phoneNumber String @db.Char(11)
  age Int
  posts Post[]
}

model Post{
  id String @id @default(uuid())
  title String @db.VarChar(200)
  content String @db.LongText

  userId String  // fk
  //fields : 어떤걸 fk로 쓸고녀 , references: User테이블에서 어떤걸로 구분할거냐
  user User @relation(fields: [userId], references: [id])
}

/src/controllers

: 라우터들을 모아놓은 폴더이다.

/src/contrillers/user

: 테이블 단위로 폴더를 분리한다.
: 해당 폴더에서는 user에 관련된 라우터와 api, user에 관한 기능들이 담겨있다.

/src/controllers/index.js

: 여기에서는 라우터들을 모아 export해주는 파일이다.

import UserController from "./users";

export default [UserController];

/src/contrillers/user/dto

: dto란 db에 접근하기 전에 한번 검사? db에 넣을 값을 dto에 전달하고 dto에서 나온 값을 db에 전달하는 형식
: dto를 만들땐 .dto.js 형식으로 파일명을 작성해준다.

/src/contrillers/user/dto/users.dto.js

export class UsersDto{
    id
    age;
    name;
    phoneNumber;
    email;
    constructor(user){
        this.id = user.id
        this.age = user.age
        this.name = user.name;
        this.phoneNumber = user.phoneNumber
        this.email = user.email
    }
}

-------------------------------------------------------
/src/contrillers/user/index.js

import {UsersDto} from "./dto";

  async getUsers(req, res, next) {
    try{
      const {users, count} = await this.userService.findUsers({skip: req.skip, take:req.take})

      res.status(200).json({users: users.map((user)=> new UsersDto(user)), count})
    }catch(err){
      next(err);
    }
  }

/src/contrillers/user/services

: services폴더는 실제로 db랑 통신하는 로직이 담겨있다.

/src/contrillers/user/services/index.js

// presmaClient모듈을 export해주는 파일을 import한다.
import database from "../../../database"

// crud를 하게될 5가지 service가 들어있음
export class UserService{
    async finduserById(id){
      					//database.테이블명.매서드()
        const user = await database.user.findUnique({
          // where : 어떤 컬럼에서                                     
            where:{
                id
            }
        })
        if(!user){
            throw {status: 404, message: "유저를 찾을 수 없습니다."}
        }
        return user
    }
    async findUsers({skip, take}){
        const users = await database.user.findMany({
            skip,
            take
        })
        //database.user.count() : user테이블에 데이터의 갯수를 모두 가져옴
        const count = await database.user.count()
        return {
            users,
            count
        }
    }
    async createUser(props){
        const newUser = await database.user.create({
            data :{
                name : props.name,
                email : props.email,
                age : props.age,
                phoneNumber : props.phoneNumber,
            }
        })
        return newUser.id
    }
    async updateUser(id){
        //에러를 핸들링해주기
        const isExist = await database.user.findUnique({
            where:{
                id
            }
        }) 
        if(!isExist){
            throw {status: 404, message:"유저를 찾을 수 없습니다."}
        }
        // 만약 테이블에서 id와 일치하는 데이터가 없는 경우 에러발생, 핸들링 불가
        await database.user.update({
            where:{
                id : isExist.id
            },
            data:{
                name : props.name,
                email : props.email,
                age : props.age,
                phoneNumber : props.phoneNumber,
            }
        })
    }
    async deleteUser(id){
      	// 해당 영역에서는 에러를 핸들링을 못해 에러를 잡는 로직을 구현해준다.
        const isExist = await database.user.findUnique({
            where:{
                id
            }
        }) 
        if(!isExist){
            throw {status: 404, message:"유저를 찾을 수 없습니다."}
        }
        await database.user.delete({
            where:{
                id: isExist.id
            }
        })
    }
}

/src/contrillers/user/index.js

: user의 모든 라우터가 담겨있는 파일이다. 해당 파일에서 dto, services등을 import해서 활용해 라우트를 작성한다.

// 라우터를 작성하기 위해
import { Router } from "express";
import { pagination } from "../../middleware/pagination";
import { CreateUserDto, UpdateUserDto, UsersDto} from "./dto";
import { UserService } from "./services";

// 라우터와 app 등록할 경로 지정을 위한 class
class UserController {
  router;
  path = "/users";
  userService

  constructor() {
    this.router = Router();
    this.init();
    // 해당 생성자 함수가 실행될때 UserService를 this.userService에 생성자 함수로 만들어준다.
    this.userService = new UserService
  }

  init() {
    //  reqeust(요청) -> application middleware -> router middleware -> apl 순서로 진행된다.
    // 라우터단위 미들웨어 사용 방법 (path, 라우터단위미들웨어, 콜백함수)
    this.router.get("/",pagination ,this.getUsers.bind(this));
    this.router.get("/:id", this.getUser.bind(this));
    this.router.post("/", this.createUser.bind(this));
    this.router.patch("/:id", this.updateUser.bind(this));
    this.router.delete("/:id", this.deleteUser.bind(this));
  }
  // reqeust(요청) -> application middleware -> router middleware -> apl 순서로 진행된다.
  //
  async getUsers(req, res, next) {
    try{
      const {users, count} = await this.userService.findUsers({skip: req.skip, take:req.take})

      res.status(200).json({users: users.map((user)=> new UsersDto(user)), count})
    }catch(err){
      next(err);
    }
  }

  async getUser(req, res, next) {
    try {
      const id = req.params;
      const user = await this.userService.finduserById(id)

      res.status(200).json({user: new UsersDto(user)})
    } catch (err) {
      next(err);
    }
  }

  async createUser(req, res, next) {
    try {
      const createUserDto = new CreateUserDto(req.body)

      const newUserId = await this.userService.createUser(createUserDto)

      res.status(201).json({id: newUserId})
    } catch (err) {
      next(err);
    }
  }

  async updateUser(req, res, next) {
    try {
      const {id} = req.params
      const updateUserDto = new UpdateUserDto(req.body)

      await this.userService.updateUser(id, updateUserDto)

      res,status(204).json({})
    } catch (err) {
      next(err);
    }
  }

  async deleteUser(req, res, next) {
    try {
      const {id} = req.params
      await this.userService.deleteUser(id)

      res.status(204).json({})
    } catch (err) {
      next(err);
    }
  }
}

const userController = new UserController();
export default userController;

src/middleware

: 미들웨어들을 뫃아놓은 폴더이다. 현재 폴더에서는 pagination.json 라는 라우터 단위 미들웨어를 만들었다. userController/index에서 사용함 2번째 인자.

src/database.js

: database에 접근 가능하게 해주는 presmaClient모듈을 import해주고 db와 직접 통신하는 로직이 담긴 service폴더에서 사용한다.

src/index.js

: express 서버를 구성하고, 어플리케이션 단위 미들웨어(app.use)를 작성하고
각각의 폴더로 분리한 라우터들을 한대 모아서 app.use로 라우터를 등록해준다.

prisma query 심화

GET

database.테이블명.findFirst()/ findMany()

- every, some, none : 1:N 관계에서 사용된다.

const user = await database.findmany({
	where:{
    	id : "1"
        // posts 테이블에서
        posts: {
        	// id가 1인 유저가 작성한 모든 posts들의 title이 "가나다라"여야지 user가 선택됨
        	every:{
            	title : "가나다라"
            },
           // id가 1인 유저가 작성한 posts들의 title이 하나라도 "가나다라"이면 user가 선택됨
            some:{
            	title : "가나다라"
            },
            // id가 1인 유저가 작성한 posts들의 title이 하나라도 "가나다라"이면 user가 선택되지 않는다.
            none:{
            	title : "가나다라"
            },
    }
})

- include : 다른 테이블과 관계가 있는 경우 사용 가능하다

const users = await database.user.findMany({
	//일단 where조건이 없기때문에 모든 user을 다 불러온다
  
  	include:{
    	posts: true
    }
})

const posts = await database.posts.findMany({
	//일단 where조건이 없기때문에 모든 user을 다 불러온다
  
  	include:{
    	user: true
    }
})

POST

bcrypt를 사용한 이미지 생성

yarn add bcrypt
yarn add dotenv

비밀번호 유효성 테스트를 위해 테이블 수정
model User{
id String @id @default(uuid())
name String @db.VarChar(200)
//이메일을 유니크값으로 변환
email String @db.VarChar(200) @unique
phoneNumber String @db.Char(11)
age Int
//password 컬럼 추가
password String

posts Post[]
}
dotenv.config()

profile
JSON 상하차 마스터 | Vue & Laravel | JS, PHP

0개의 댓글