[서버 week_3] CRUD API 구현_CREATE

SH·2022년 5월 3일
0

서버 세미나

목록 보기
7/14

프로젝트 세팅까지 마쳤으면 이제 mvc모델(스프링에서는 이렇게 부르는데 node.js는 다른걸로 알고있음)로 어플리케이션을 설계해야 한다. 설계할 때 크게 3가지 계층으로 구현한다

3-layer-architecture

  1. controller - req, res를 여기서 받아서 처리
  2. service - conntroller에서 받은 model을 가공, db 레이어에 넘겨줌
  3. db - 데이터 저장 및 관리
    +model - 스프링에서의 도메인 객체

스프링에서는 controller, service, repository(db에 접근하지 db 자체랑은 다름), modelAndView 로 나뉜다. 계층 구조가 조금 다른다.


어플리케이션 구현 순서

예제에서는 게시글 어플리케이션 api를 구현할 생각이다. 모델은 user, post가 있다.

0. 응답 메시지에 필요한 module 구현

// src/module/StatusCode.ts
// object로 정의
const StatusCode = {
    OK: 200,
    CREATED: 201,
    NO_CONTENT: 204,
    BAD_REQUEST: 400,
    UNAUTHORIZED: 401,
    FORBIDDEN: 403,
    NOT_FOUND: 404,
    CONFLICT: 409,
    INTERNAL_SERVER_ERROR: 500,
    SERVICE_UNAVAILABLE: 503,
    DB_ERROR: 600,
};

export default StatusCode;
// src/module/Message.ts
// object로 생성

const Message = {
    NULL_VALUE: '필요한 값이 없습니다.',
    NOT_FOUND: '존재하지 않는 자원',
    BAD_REQUEST: '잘못된 요청',
    INTERNAL_SERVER_ERROR: '서버 내부 오류',
    
    //USER 
    CREATE_USER_SUCCESS: '유저 생성 성공',
    READ_USER_SUCCESS:  '유저 조회 성공',
    READ_USER_FAIL: '유저 조회 실패',
    UPDATE_USER_SUCCESS: '유저 수정 성공',
    DELETE_USER_SUCCESS: '유저 삭제 성공',

    //POST
    CREATE_POST_SUCCESS: '게시글 생성 성공',
    READ_POST_SUCCESS: '게시글 조회 성공',
    READ_POST_FAIL: '게시글 조회 실패',
    UPDATE_POST_SUCCESS: '게시글 수정 성공',
    DELETE_POST_SUCCESS: '게시글 삭제 성공',
}

export default Message;
// src/module/Utils.ts
// object 안에 함수로 선언

const Utils = {
    success: (status: Number, message: String, data?: any) => {
        return {
            status,
            message,
            success: true,
            data
        }
    }, 
    fail: (status: Number, message: String) => {
        return {
            status,
            message,
            success: false
        }
    }
}

export default Utils;

1. model과 schema 구현

schema - 컬렉션에 들어가는 문서 내부의 각 필드가 어떤 형식으로 되어 있는지 정의하는 객체
model - 스키마를 사용하여 만드는 인스턴스로, 데이터베이스에서 실제 작업을 처리할 수 있는 함수들을 지니고 있는 객체


출처: https://velog.io/@woody/mongoose-%EC%8A%A4%ED%82%A4%EB%A7%88%EC%99%80-%EB%AA%A8%EB%8D%B8


+mongoose에서 나오는 다른 기본 용어 정리
collection - rdb에서의 table
document - rdb에서의 rows(가로로 한 줄)
field - rdb에서의 column


왜 구현함?

userInfo.ts - 모델의 대략적인 속성과 타입 정의 + model 객체 만들 때 필요
userSchema - 스키마 객체 만들어서 mongodb한테 "모델 이렇게 만들어줘야한다~"고 알려줘야함
userModel - 이 model 객체 안에 findByIdAndUpdate, findById 등등 메소드가 있음. service 계층에서 이 메소드를 사용하려면 export default로 내보내줘야함


구현 코드

일단 interfaces 파일 안에 model 객체의 정보를 인터페이스 형태로 구현해준다.(interface/user.info.ts처럼)

export interface UserInfo {
    id: String;
    name: String; 
    password: String; 
    token: String; // 작성자 토큰
}
export interface post {
    title: String;
    writer: String; // 작성자의 토큰
    content: String;
    noticeToken: String; // 글 고유 토큰
}

그리고 model 폴더 안에 moongose 객체로 schema 객체 만들고 model 객체를 service 계층에서 만들 수 있도록 export 해준다. (model/user.ts)

// schema 객체 만들기
const 스키마이름 = new mongoose.Schema({
	필드1: {
    	type: 타입지정, 
        다른속성: 다른속성지정
    }, 
    필드2: {
		type: 타입지정,
        다른속성: 다른속성지정
    }
})

// model 내보내기
export default mongoose.model<인포파일 & mongoose.documtent>("db에서 사용될 model이름", 스키마이름);
import mongoose from "mongoose";
import { UserInfo } from "../interfaces/user/UserInfo";

// 스키마. 스키마는 Info 인터페이스에 정의된 프로퍼티를 바탕으로 각 필드별 타입과 속성을 지정해준다
const userSchema = new mongoose.Schema({
    id: {
        type: String,
        required: true
    },
    name: {
        type: String,
        required: true
    }, 
    password: {
        type: String,
        required: true
    }, 
    token: {
        type: String
    }
});

// 정의된 스키마 객체를 바탕으로 model을 만들어서 export (타입은 Info 인터페이스와 mongoose document)
export default mongoose.model<UserInfo & mongoose.Document>("User", userSchema);
import mongoose from "mongoose";
import { PostInfo } from "../interfaces/post/PostInfo";

const postSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    writer: {
        type: String, 
        required: true 
    }, 
    content: {
        type: String,
        required : true
    }, 
    noticeToken: { 
        type: String,
        required : true 
    }
}) 

export default mongoose.model<PostInfo & mongoose.Document>("Post", postSchema);

이제 본격적으로 api를 구현해볼 것이다
일단 각 리소스별 URL는 다음과 같다

user
POST /user
PUT /user/:userId
GET /user/:userId
DELETE /user/:userId

post
POST /blog
PUT /blog/:postId
GET /blog/:postId
DELETE /blog/:postId


일단 service에서 함수를 만들고 controller에서 그걸 사용한 다음에 router을 구현해주는 순서대로 하는 게 편리하다는걸 참고하셈요


2. CRUD - Create 구현

+) 진짜 내가 새로운 플젝 하나 혼자서 해볼려고 했는데 진짜 후 뇌가 없는 사람같이 reset 남발하다가 실수로 local을 다 밀어버렸다 커밋 되어있어서 걍 받아오려고 했는데 submodule로 push되어있고 다시 받아지지도 않아서 결국 과제 2개를 처음부터 다시 하게 되었다 아놔ㅎㅎㅎㅎㅎ

결론: 위에 선언했던 post랑 user 필드와 model은 이 블로그 버전이고
지금부터 보여주는 api 예제 에 사용된 model은 동아리에서 했던 모델 버전이다 하하하


// src/service/UserService.ts

import { UserCreateDto } from "../interfaces/user/UserCreateDto";
import User from "../models/User";

const createUser = async (userCreateDto: UserCreateDto) => {

    try {

        const user = new User({
            name: userCreateDto.name,
            phone: userCreateDto.phone,
            email: userCreateDto.email,
            age: userCreateDto.age,
            school: userCreateDto.school
        });

        await user.save();

        // user를 db에 저장하고 id값을 가져와서 반환하는듯
        const data = {
            _id: user._id
        };

        return data;

    } catch (error) {
        console.log(error);
        throw error;

    }
    
}


export default {
    createUser,
}

userCreateDto 형식으로 받아온 객체에서 데이터만 빼서 새 user에 넣어주고 save()한다. 이후에 data 변수에 user의 id를 가져오고 반환한다. 에러가 발생할 경우 error 던져준다. userCreateDto는 git 참고


// src/controllers/UserController.ts

const createUser = async (req: Request, res: Response) => {
    const userCreateDto: UserCreateDto = req.body;

    try {
    
        const data: PostBaseResponseDto = await UserService.createUser(userCreateDto);
        res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, message.USER_CREATED, data));
    
    } catch(error) {
        console.log(error)
        res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.success(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR));
        

    }
    
}

export default {
    createUser,
}

PostBaseResponseDto는 다음과 같다

import mongoose from "mongoose";

export interface PostBaseResponseDto {
    _id: mongoose.Schema.Types.ObjectId; // ObjectId 가져오는 코드
}

이제 router에서 get 메소드로 "/user" 요청이 왔을 때 userController의 createUser가 시행되도록 연결만 해주면 된다.


전체적인 프로세스는 다음과 같다

src/index.ts가 가장 먼저 실행. // app.use(routes);
-> src/routes/index.ts에서 "/user" 엔드포인트가 오면 UserRouter.ts로 이동
-> src/routes/UserRouter.ts에서 get 메소드라면 UserController.crateUser() 호출
-> src/controller/index.ts 파일에서 UserController export해줌
-> src/controller/UserController.ts 파일에서 UserServce.createUser() 호출
-> src/user/index.ts 파일에서 UserService export해줌
-> src/user/UserService.ts 파일에서 save 로직 수행


각 레이어별로 index.ts 파일이 가장 먼저 실행된다는 점, controller와 service가 여기서 export 된다는 점을 주의하자! 파일은 생략하겠다

update, delete, get은 다음 포스트에

profile
블로그 정리안하는 J개발자

0개의 댓글