프로젝트 세팅까지 마쳤으면 이제 mvc모델(스프링에서는 이렇게 부르는데 node.js는 다른걸로 알고있음)로 어플리케이션을 설계해야 한다. 설계할 때 크게 3가지 계층으로 구현한다
스프링에서는 controller, service, repository(db에 접근하지 db 자체랑은 다름), modelAndView 로 나뉜다. 계층 구조가 조금 다른다.
예제에서는 게시글 어플리케이션 api를 구현할 생각이다. 모델은 user, post가 있다.
// 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;
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을 구현해주는 순서대로 하는 게 편리하다는걸 참고하셈요
+) 진짜 내가 새로운 플젝 하나 혼자서 해볼려고 했는데 진짜 후 뇌가 없는 사람같이 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은 다음 포스트에