Node.js API를 위한 더 나은 아키텍처 설계
본문
얼마 전에 Node.js, React.js 및 MongoDB를 사용하여 Fullstack 프로젝트를 만드는 것에 대한 게시물을 작성했습니다. 이것은 기초를 익히고 실행하는 데 도움이 되는 매우 멋진 시작 프로젝트입니다.
https://dev.to/pacheco/designing-a-better-architecture-for-a-node-js-api-24d
그러나 더 큰 아키텍처를 구현하는 것은 매우 중요합니다. 특히 대규모 프로젝트가 있고 대규모 팀과 함께 작업하는 경우 더욱 그렇습니다. 이를 통해 프로젝트를 쉽게 개발하고 유지할 수 있습니다.
따라서 이 게시물의 목표는 현재 API 아키텍처와 더 나은 구조를 만들고 디자인 패턴과 깨끗한 코드를 적용하는 방법을 공유하는 것입니다.
먼저 작업 폴더와 초기 파일을 만들어 봅시다.
$ mkdir node-starter
$ cd node-starter
$ touch index.js
$ npm init -y
구조 만들기
이제 프로젝트의 기본 폴더를 만들어 봅시다
$ mkdir config src src/controllers src/models src/services src/helpers
의존성 추가
이 프로젝트에서는 Express와 MongoDB를 사용할 것이므로 초기 종속성을 추가하겠습니다.
$ npm install --save body-parser express mongoose mongoose-unique-validator slugify
DEV 종속성 추가
이 프로젝트에서 최신 ES6 구문을 사용하려면 babel을 추가하고 구성 해 보겠습니다.
npm i -D @babel/node @babel/core @babel/preset-env babel-loader nodemon
바벨 설정
기본 폴더에서 다음 코드를 사용하여 .babelrc라는 파일을 만듭니다.
{
"presets": [
"@babel/preset-env"
]
}
이제 package.json으로 이동하여 다음 스크립트를 추가하십시오
"scripts": {
"start": "babel-node index.js",
"dev:start": "clear; nodemon --exec babel-node index.js"
}
서버 만들기
config 폴더 아래에 다음 코드를 사용하여 server.js라는 파일을 만듭니다.
import express from "express";
import bodyParser from "body-parser";
const server = express();
server.use(bodyParser.json());
export default server;
이제 서버 설정을 index.js 파일로 가져옵니다 :
import server from './config/server';
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
console.log(`app running on port ${PORT}`);
});
이제 다음 스크립트를 사용하여 서버를 실행할 수 있어야 합니다.
$ npm run dev:start
그리고 당신은 다음과 같은 응답을 받아야 합니다 :
[nodemon] 1.19.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `babel-node index.js`
app running on port 5000
데이터베이스 설정
이제 데이터베이스를 설정해 봅시다.
이를 위해서는 로컬 컴퓨터에서 MongoDB를 설치하고 실행해야 합니다.
구성에서 database.js 파일을 추가하십시오.
//database.js
import mongoose from "mongoose";
class Connection {
constructor() {
const url =
process.env.MONGODB_URI || `mongodb://localhost:27017/node-starter`;
console.log("Establish new connection with url", url);
mongoose.Promise = global.Promise;
mongoose.set("useNewUrlParser", true);
mongoose.set("useFindAndModify", false);
mongoose.set("useCreateIndex", true);
mongoose.set("useUnifiedTopology", true);
mongoose.connect(url);
}
}
export default new Connection();
여기서는 새 Connection을 내보내 데이터베이스의 단일 인스턴스를 작성합니다. 이것은 이와 같이 내보낼 때 노드에 의해 자동으로 처리되며 응용 프로그램에 이 클래스의 단일 인스턴스가 하나만 있도록 합니다.
이제 index.js 파일의 시작 부분에서 바로 가져옵니다.
//index.js
import './config/database';
//...
모델 만들기
이제 첫 번째 모델을 만들어 봅시다. src/models에서 다음 내용으로 Post.js라는 파일을 만듭니다.
//src/models/Post.js
import mongoose, { Schema } from "mongoose";
import uniqueValidator from "mongoose-unique-validator";
import slugify from 'slugify';
class Post {
initSchema() {
const schema = new Schema({
title: {
type: String,
required: true,
},
slug: String,
subtitle: {
type: String,
required: false,
},
description: {
type: String,
required: false,
},
content: {
type: String,
required: true,
}
}, { timestamps: true });
schema.pre(
"save",
function(next) {
let post = this;
if (!post.isModified("title")) {
return next();
}
post.slug = slugify(post.title, "_");
console.log('set slug', post.slug);
return next();
},
function(err) {
next(err);
}
);
schema.plugin(uniqueValidator);
mongoose.model("posts", schema);
}
getInstance() {
this.initSchema();
return mongoose.model("posts");
}
}
export default Post;
서비스 만들기
API에 대한 모든 공통 기능을 가지면서 다른 서비스가 이를 상속 할 수 있도록 하는 Service 클래스를 작성해 봅시다.
src/services 폴더에 Service.js 파일을 작성하십시오.
//src/services/Service.js
import mongoose from "mongoose";
class Service {
constructor(model) {
this.model = model;
this.getAll = this.getAll.bind(this);
this.insert = this.insert.bind(this);
this.update = this.update.bind(this);
this.delete = this.delete.bind(this);
}
async getAll(query) {
let { skip, limit } = query;
skip = skip ? Number(skip) : 0;
limit = limit ? Number(limit) : 10;
delete query.skip;
delete query.limit;
if (query._id) {
try {
query._id = new mongoose.mongo.ObjectId(query._id);
} catch (error) {
console.log("not able to generate mongoose id with content", query._id);
}
}
try {
let items = await this.model
.find(query)
.skip(skip)
.limit(limit);
let total = await this.model.count();
return {
error: false,
statusCode: 200,
data: items,
total
};
} catch (errors) {
return {
error: true,
statusCode: 500,
errors
};
}
}
async insert(data) {
try {
let item = await this.model.create(data);
if (item)
return {
error: false,
item
};
} catch (error) {
console.log("error", error);
return {
error: true,
statusCode: 500,
message: error.errmsg || "Not able to create item",
errors: error.errors
};
}
}
async update(id, data) {
try {
let item = await this.model.findByIdAndUpdate(id, data, { new: true });
return {
error: false,
statusCode: 202,
item
};
} catch (error) {
return {
error: true,
statusCode: 500,
error
};
}
}
async delete(id) {
try {
let item = await this.model.findByIdAndDelete(id);
if (!item)
return {
error: true,
statusCode: 404,
message: "item not found"
};
return {
error: false,
deleted: true,
statusCode: 202,
item
};
} catch (error) {
return {
error: true,
statusCode: 500,
error
};
}
}
}
export default Service;
이 서비스에서는 애플리케이션의 기본 기능 (기본 CRUD)을 작성하고 항목을 가져오고 삽입, 업데이트 및 삭제하는 기능을 추가했습니다.
이제 Post 서비스를 만들고 방금 만든 이 모든 기능을 상속합시다. src/services에서 다음 컨텐츠를 사용하여 PostService.js 파일을 작성하십시오.
//src/services/PostService
import Service from './Service';
class PostService extends Service {
constructor(model) {
super(model);
}
};
export default PostService;
그것은 우리가 기본 Service.js 파일에서 생성 한 모든 기능을 상속하며 다른 모든 엔드 포인트에 대해 API에서 반복 될 수 있는 것처럼 간단합니다.
컨트롤러 만들기
우리는 서비스를 생성 할 때와 같은 원칙을 따를 것입니다. 여기서는 모든 공통 기능을 갖고 다른 컨트롤러가 이를 상속하게 하는 기본 Controller.js 파일을 생성 할 것입니다.
src/controllers 아래에 Controller.js 파일을 작성하고 다음 코드를 추가하십시오.
//src/controllers/Controller.js
class Controller {
constructor(service) {
this.service = service;
this.getAll = this.getAll.bind(this);
this.get = this.get.bind(this);
this.insert = this.insert.bind(this);
this.update = this.update.bind(this);
this.delete = this.delete.bind(this);
}
async getAll(req, res) {
return res.status(200).send(await this.service.getAll(req.query));
}
async insert(req, res) {
let response = await this.service.insert(req.body);
if (response.error) return res.status(response.statusCode).send(response);
return res.status(201).send(response);
}
async update(req, res) {
const { id } = req.params;
let response = await this.service.update(id, req.body);
return res.status(response.statusCode).send(response);
}
async delete(req, res) {
const { id } = req.params;
let response = await this.service.delete(id);
return res.status(response.statusCode).send(response);
}
}
export default Controller;
이제 src/controllers 아래에 PostController 파일을 만들어 봅시다
//src/controllers/PostController.js
import Controller from './Controller';
import PostService from "./../services/PostService";
import Post from "./../models/Post";
const postService = new PostService(
new Post().getInstance()
);
class PostController extends Controller {
constructor(service) {
super(service);
}
}
export default new PostController(postService);
여기서 우리는 원하는 서비스와 모델을 가져오고 있으며 Post 모델 인스턴스를 생성자에게 전달하는 Post 서비스 인스턴스도 만들고 있습니다.
경로 만들기
이제 API에 대한 경로를 만들 차례입니다.
구성 폴더 아래에 routes.js 파일을 만듭니다.
//config/routes.js
import PostController from './../src/controllers/PostController';
export default (server) => {
// POST ROUTES
server.get(`/api/post`, PostController.getAll);
server.get(`/api/post/:params`, PostController.get);
server.post(`/api/post`, PostController.insert)
server.put(`/api/post/:id`, PostController.update);
server.delete(`/api/post/:id`, PostController.delete);
}
이 파일은 Post 컨트롤러를 가져오고 함수를 원하는 경로에 매핑합니다.
이제 다음과 같이 body 파서 설정 직후에 route를 server.js 파일로 가져와야 합니다.
//config/server.js
//...
import setRoutes from "./routes";
setRoutes(server);
//...
이 시점에서 생성 된 모든 경로를 요청할 수 있으므로 테스트 해 보겠습니다.
다음 json 본문을 사용하여 /api/post 경로에 대한 POST 요청을 작성하십시오. 이 작업에 Postman 또는 Insomnia와 같은 API 클라이언트를 사용할 수 있습니다.
{
"title": "post 1",
"subtitle": "subtitle post 1",
"content": "content post 1"
}
다음과 같은 것을 얻어야 합니다.
{
"error": false,
"item": {
"_id": "5dbdea2e188d860cf3bd07d1",
"title": "post 1",
"subtitle": "subtitle post 1",
"content": "content post 1",
"createdAt": "2019-11-02T20:42:22.339Z",
"updatedAt": "2019-11-02T20:42:22.339Z",
"slug": "post_1",
"__v": 0
}
}
결론
API 아키텍처를 설계하는 방법에는 여러 가지가 있으며, 목표는 항상 깨끗하고 재사용 가능한 코드를 유지하고, 자신을 반복하지 않고 다른 사람들이 쉽게 작업 할 수 있도록 돕는 것입니다. 또한 유지 관리 및 새로운 추가에 도움이 될 것입니다 기능.
소스 코드는 여기에서 찾을 수 있습니다
- 이전글간단한 채팅 앱을 구축하여 WebSockets 배우기 19.11.09
- 다음글Kotlin을 사용하여 기본 Android 앱을 빌드하는 방법 19.11.09