정리가 필요한 게시글
바벨의 사용법
바벨을 설치한다
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())
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 - 로그인한 유저 정보를 토큰으로 만들기 위해 사용
둘다 암호화를 시키고 토큰은 유효기간이 있음
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
설치
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"
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])
}
// .env
// 초기값
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
DATABASE_URL="mysql://root:password@localhost:3306/lecture"
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에 연결이 된 후 나머지 로직들이 작동되게끔
// 연결해준다.
테이블 단위로 폴더를 구성해주면 리펙토링이나 가독성면에서 효과적(개인취향)
database get
database.모델명(테이블명).findUnique()
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
const user = await database.user.findMany({
where:{
job: dev
},
orderBy:{
age:"desc"
}
// job이 dev인 데이터의 age가 내림차순으로 불러옴
skip / take
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()
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인 데이터를 삭제
: 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])
}
: 라우터들을 모아놓은 폴더이다.
: 테이블 단위로 폴더를 분리한다.
: 해당 폴더에서는 user에 관련된 라우터와 api, user에 관한 기능들이 담겨있다.
: 여기에서는 라우터들을 모아 export해주는 파일이다.
import UserController from "./users";
export default [UserController];
: 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);
}
}
: 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
}
})
}
}
: 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;
: 미들웨어들을 뫃아놓은 폴더이다. 현재 폴더에서는 pagination.json 라는 라우터 단위 미들웨어를 만들었다. userController/index에서 사용함 2번째 인자.
: database에 접근 가능하게 해주는 presmaClient모듈을 import해주고 db와 직접 통신하는 로직이 담긴 service폴더에서 사용한다.
: express 서버를 구성하고, 어플리케이션 단위 미들웨어(app.use)를 작성하고
각각의 폴더로 분리한 라우터들을 한대 모아서 app.use로 라우터를 등록해준다.
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 : "가나다라"
},
}
})
const users = await database.user.findMany({
//일단 where조건이 없기때문에 모든 user을 다 불러온다
include:{
posts: true
}
})
const posts = await database.posts.findMany({
//일단 where조건이 없기때문에 모든 user을 다 불러온다
include:{
user: true
}
})
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()