Node / Express로 엔터프라이즈 앱 빌드
Node / Express / Mongo (실제로 MEAN 스택 사용)를 사용하여 엔터프라이즈 응용 프로그램을 구성하는 방법을 이해하려고합니다.
2 권의 책과 인터넷 검색 (유사한 StackOverflow 질문 포함)을 읽은 후 Express를 사용하여 대규모 애플리케이션을 구성하는 좋은 예를 찾을 수 없었습니다. 내가 읽은 모든 소스는 다음 엔티티로 애플리케이션을 분할하는 것이 좋습니다.
- 노선
- 컨트롤러
- 모델
그러나이 구조에서 내가 보는 주요 문제는 컨트롤러가 신 객체와 같고 req
, res
객체 에 대해 알고 있으며 유효성 검사를 담당하고 비즈니스 로직이 포함되어 있다는 것입니다.
다른 측면에서 라우트는 엔드 포인트 (경로)를 컨트롤러 메서드에 매핑하는 것이기 때문에 오버 엔지니어링처럼 보입니다.
저는 Scala / Java 배경이 있으므로 컨트롤러 / 서비스 / dao의 3 계층으로 모든 로직을 분리하는 습관이 있습니다.
나를 위해 다음 진술이 이상적입니다.
컨트롤러는 웹 부분과의 상호 작용, 즉 마샬링 / 마샬링 해제, 간단한 유효성 검사 (필수, 최소, 최대, 이메일 정규식 등) 만 담당합니다.
서비스 계층 (실제로 NodeJS / Express 앱에서 놓친)은 비즈니스 로직, 일부 비즈니스 유효성 검사 만 담당합니다. 서비스 계층은 웹 부분에 대해 아무것도 알지 못합니다 (즉, 웹 컨텍스트뿐만 아니라 다른 응용 프로그램에서 호출 할 수 있음).
DAO 레이어에 관해서는 모두 명확합니다. 몽구스 모델은 실제로 DAO이므로 여기에서 가장 분명합니다.
내가 본 예제는 매우 간단하고 Node / Express의 개념 만 보여 주지만 비즈니스 로직 / 검증이 많이 포함 된 실제 예제를보고 싶습니다.
편집하다:
나에게 명확하지 않은 또 다른 것은 DTO 개체가 없다는 것입니다. 이 예를 고려하십시오.
const mongoose = require('mongoose');
const Article = mongoose.model('Article');
exports.create = function(req, res) {
// Create a new article object
const article = new Article(req.body);
// saving article and other code
}
req.body
Mongo 문서를 생성하기위한 매개 변수로 JSON 객체 가 전달됩니다. 나에게 나쁜 냄새가 난다. 원시 JSON이 아닌 구체적인 클래스로 작업하고 싶습니다.
감사.
컨트롤러는 여러분이 원하지 않을 때까지 신의 대상입니다 ...
– zurfyx (╯ ° □ °) ╯︵ ┻━┻
솔루션에 관심이 있으십니까? 에 이동 최신 섹션 "결과" .
┬──┬◡ ノ (°-° ノ)
답변을 시작하기 전에이 답변을 일반적인 SO 길이보다 길게 만든 것에 대해 사과하겠습니다. 컨트롤러만으로는 아무것도하지 않으며 전체 MVC 패턴에 관한 것입니다. 따라서 최소한의 책임으로 적절한 격리 컨트롤러를 달성하는 방법을 보여주기 위해 라우터 <-> 컨트롤러 <-> 서비스 <-> 모델에 대한 모든 중요한 세부 정보를 살펴 보는 것이 적절하다고 느꼈습니다.
가상의 경우
작은 가상 사례부터 시작해 보겠습니다.
- AJAX를 통해 사용자 검색을 제공하는 API를 갖고 싶습니다.
- Socket.io를 통해 동일한 사용자 검색을 제공하는 API를 갖고 싶습니다.
Express부터 시작하겠습니다. 그것은 쉬운 일입니다, 그렇지 않습니까?
route.js
import * as userControllers from 'controllers/users';
router.get('/users/:username', userControllers.getUser);
controllers / user.js
import User from '../models/User';
function getUser(req, res, next) {
const username = req.params.username;
if (username === '') {
return res.status(500).json({ error: 'Username can\'t be blank' });
}
try {
const user = await User.find({ username }).exec();
return res.status(200).json(user);
} catch (error) {
return res.status(500).json(error);
}
}
이제 Socket.io 부분을 수행해 보겠습니다.
socket.io 질문 이 아니므로 상용구를 건너 뛰겠습니다.
import User from '../models/User';
socket.on('RequestUser', (data, ack) => {
const username = data.username;
if (username === '') {
ack ({ error: 'Username can\'t be blank' });
}
try {
const user = User.find({ username }).exec();
return ack(user);
} catch (error) {
return ack(error);
}
});
음, 여기 냄새가 ...
if (username === '')
. 컨트롤러 유효성 검사기를 두 번 작성해야했습니다.n
컨트롤러 검증자가 있다면 어떨까요? 각 사본을 두 개 (또는 그 이상) 최신 상태로 유지해야합니까?User.find({ username })
두 번 반복됩니다. 그것은 아마도 서비스 일 수 있습니다.
우리는 각각 Express와 Socket.io의 정확한 정의에 첨부 된 두 개의 컨트롤러를 작성했습니다. Express와 Socket.io는 모두 이전 버전과의 호환성을 갖는 경향이 있기 때문에 평생 동안 결코 깨지지 않을 것입니다. 그러나 재사용 할 수 없습니다. Hapi 용 Express 변경 ? 모든 컨트롤러를 다시 실행해야합니다.
분명하지 않을 수도있는 또 다른 나쁜 냄새 ...
컨트롤러 응답은 수작업으로 이루어집니다. .json({ error: whatever })
RL의 API는 지속적으로 변경됩니다. 앞으로는 다음 { err: whatever }
과 같은 응답을 원 하거나 더 복잡하고 유용 할 수 있습니다.{ error: whatever, status: 500 }
시작합시다 (가능한 해결책)
나는 그것을 호출 할 수 없습니다 솔루션의 끝없는 양이 부족 있기 때문에 솔루션입니다. 그것은 당신의 창의성과 당신의 필요에 달려 있습니다. 다음은 괜찮은 솔루션입니다. 나는 비교적 큰 프로젝트에서 사용하고 있으며 잘 작동하는 것처럼 보이며 이전에 지적한 모든 것을 수정합니다.
모델-> 서비스-> 컨트롤러-> 라우터로 이동하여 끝까지 흥미롭게 유지합니다.
모델
질문의 주제가 아니기 때문에 모델에 대해 자세히 설명하지 않겠습니다.
다음과 유사한 Mongoose 모델 구조가 있어야합니다.
models / User / validate.js
export function validateUsername(username) {
return true;
}
여기 에서 mongoose 4.x 유효성 검사기의 적절한 구조에 대해 자세히 알아볼 수 있습니다 .
models / User / index.js
import { validateUsername } from './validate';
const userSchema = new Schema({
username: {
type: String,
unique: true,
validate: [{ validator: validateUsername, msg: 'Invalid username' }],
},
}, { timestamps: true });
const User = mongoose.model('User', userSchema);
export default User;
사용자 이름 필드와 created
updated
몽구스 제어 필드가 있는 기본 사용자 스키마입니다 .
validate
여기 에 필드를 포함시킨 이유 는 컨트롤러가 아닌 여기에서 대부분의 모델 유효성 검사를 수행해야한다는 것을 알기위한 것입니다.
Mongoose Schema는 데이터베이스에 도달하기 전 마지막 단계입니다. 누군가가 MongoDB를 직접 쿼리하지 않는 한 모든 사람이 모델 유효성 검사를 거치므로 컨트롤러에 배치하는 것보다 더 많은 보안을 제공합니다. 이전 예제에서와 같이 단위 테스트 유효성 검사기가 사소하다는 것은 아닙니다.
여기 와 여기에서 이에 대한 자세한 내용을 읽어보십시오 .
서비스
서비스는 프로세서 역할을합니다. 허용 가능한 매개 변수가 주어지면 매개 변수를 처리하고 값을 반환합니다.
대부분의 경우 (이것을 포함하여) Mongoose 모델 을 사용 하고 Promise (또는 콜백)를 반환합니다 . 그러나 아직 그렇게하지 않으면 Promises와 함께 ES6을 확실히 사용할 것 입니다.
services / user.js
function getUser(username) {
return User.find({ username}).exec(); // Just as a mongoose reminder, .exec() on find
// returns a Promise instead of the standard callback.
}
이 시점에서 당신은 궁금 할 것입니다 catch
. 아니요, 나중에 멋진 트릭 을 할 것이며이 경우에는 사용자 지정 트릭이 필요하지 않기 때문입니다.
다른 경우에는 사소한 동기화 서비스로 충분합니다. 동기화 서비스에 I / O가 포함되어 있지 않은지 확인하십시오. 그렇지 않으면 전체 Node.js 스레드를 차단하게됩니다 .
services / user.js
function isChucknorris(username) {
return ['Chuck Norris', 'Jon Skeet'].indexOf(username) !== -1;
}
제어 장치
중복 된 컨트롤러를 피하고 싶으므로 각 작업에 대한 컨트롤러 만 있습니다 .
controllers / user.js
export function getUser(username) {
}
이 서명은 지금 어떻게 생겼습니까? 예쁘죠? 사용자 이름 매개 변수에만 관심이 있기 때문에 req, res, next
.
누락 된 유효성 검사기와 서비스를 추가해 보겠습니다.
controllers / user.js
import { getUser as getUserService } from '../services/user.js'
function getUser(username) {
if (username === '') {
throw new Error('Username can\'t be blank');
}
return getUserService(username);
}
여전히 깔끔해 보이지만 ... throw new Error
는 어떻습니까? 내 응용 프로그램이 충돌하지 않습니까? -쉿, 기다려. 아직 끝나지 않았습니다.
따라서이 시점에서 컨트롤러 문서는 다음과 같이 보입니다.
/**
* Get a user by username.
* @param username a string value that represents user's username.
* @returns A Promise, an exception or a value.
*/
에 명시된 "값"은 무엇입니까 @returns
? 이전에 서비스가 동기화 또는 비동기 (를 사용 Promise
) 일 수 있다고 말한 것을 기억 하십니까? getUserService
이 경우 비동기이지만 isChucknorris
서비스는 그렇지 않으므로 단순히 Promise 대신 값을 반환합니다.
모두가 문서를 읽을 수 있기를 바랍니다. 일부 컨트롤러를 다른 컨트롤러와 다르게 처리해야하고 일부 컨트롤러에는 try-catch
블록 이 필요하기 때문 입니다.
먼저 시도하기 전에 문서를 읽는 개발자 (여기에는 저를 포함)를 신뢰할 수 없으므로이 시점에서 결정을 내려야합니다.
Promise
반환 을 강제하는 컨트롤러- 항상 약속을 돌려주는 서비스
⬑ 이것은 일관성없는 컨트롤러 반환을 해결할 것입니다 (try-catch 블록을 생략 할 수 있다는 사실이 아님).
IMO, 나는 첫 번째 옵션을 선호합니다. 컨트롤러는 대부분의 경우 가장 많은 약속을 연결하는 컨트롤러이기 때문입니다.
return findUserByUsername
.then((user) => getChat(user))
.then((chat) => doSomethingElse(chat))
ES6 Promise를 사용하는 경우 다음과 같은 멋진 속성을 대신 사용할 수 있습니다 Promise
. Promise
수명 동안 비 약속을 처리 할 수 있으며 여전히 다음을 반환 할 수 있습니다 Promise
.
return promise
.then(() => nonPromise)
.then(() => // I can keep on with a Promise.
우리가 호출하는 유일한 서비스가를 사용하지 않으면 Promise
우리가 직접 만들 수 있습니다.
return Promise.resolve() // Initialize Promise for the first time.
.then(() => isChucknorris('someone'));
예제로 돌아 가면 결과는 다음과 같습니다.
...
return Promise.resolve()
.then(() => getUserService(username));
이미 Promise를 반환하므로이 Promise.resolve()
경우에는 실제로 필요하지 않지만 getUserService
일관성을 유지하고 싶습니다.
catch
블록 에 대해 궁금한 경우 : 사용자 지정 처리를 원하지 않는 한 컨트롤러에서 사용하고 싶지 않습니다. 이렇게하면 이미 내장 된 두 개의 통신 채널 (오류 예외 및 성공 메시지 반환)을 사용하여 개별 채널을 통해 메시지를 전달할 수 있습니다.
ES6 Promise 대신 컨트롤러에서 .then
최신 ES2017 async / await
( 현재 공식 )을 사용할 수 있습니다 .
async function myController() {
const user = await findUserByUsername();
const chat = await getChat(user);
const somethingElse = doSomethingElse(chat);
return somethingElse;
}
공지 사항 async
의 앞 function
.
라우터
마지막으로 라우터, 예!
그래서 우리는 아직 사용자에게 아무 응답도하지 않았고, 우리가 가진 것은 항상 Promise
(데이터와 함께) 반환한다는 것을 알고있는 컨트롤러뿐입니다 . 오!, 그리고 throw new Error is called
일부 서비스 Promise
가 중단 되면 예외가 발생할 수 있습니다 .
라우터는, 균일 한 방식으로, 클라이언트 제어 민원 및 반환 데이터가 어떤 기존의 데이터를 한 것 일 것이다 null
하거나 undefined
data
또는 오류.
라우터는 여러 정의를 가질 유일한 것입니다. 그 수는 인터셉터에 따라 달라집니다. 가상의 경우에는 API (익스프레스 포함)와 소켓 (Socket.io 포함)이 있습니다.
우리가해야 할 일을 검토해 보겠습니다.
우리는 변환에 대한 우리의 라우터 원하는 (req, res, next)
에를 (username)
. 순진한 버전은 다음과 같습니다.
router.get('users/:username', (req, res, next) => {
try {
const result = await getUser(req.params.username); // Remember: getUser is the controller.
return res.status(200).json(result);
} catch (error) {
return res.status(500).json(error);
}
});
잘 작동하지만 모든 경로에이 스 니펫을 복사하여 붙여 넣으면 엄청난 양의 코드 중복이 발생합니다. 그래서 우리는 더 나은 추상화를 만들어야합니다.
이 경우 각 경로에서하는 것처럼 약속과 n
매개 변수를 사용하고 라우팅 및 return
작업을 수행 하는 일종의 가짜 라우터 클라이언트를 만들 수 있습니다 .
/**
* Handles controller execution and responds to user (API Express version).
* Web socket has a similar handler implementation.
* @param promise Controller Promise. I.e. getUser.
* @param params A function (req, res, next), all of which are optional
* that maps our desired controller parameters. I.e. (req) => [req.params.username, ...].
*/
const controllerHandler = (promise, params) => async (req, res, next) => {
const boundParams = params ? params(req, res, next) : [];
try {
const result = await promise(...boundParams);
return res.json(result || { message: 'OK' });
} catch (error) {
return res.status(500).json(error);
}
};
const c = controllerHandler; // Just a name shortener.
이 트릭 에 대해 더 알고 싶다면 React-Redux와 socket.io가있는 Websockets ( " SocketClient.js "섹션)의 다른 답장에서 이것의 전체 버전에 대해 읽을 수 있습니다 .
귀하의 경로는 controllerHandler
?
router.get('users/:username', c(getUser, (req, res, next) => [req.params.username]));
처음처럼 깨끗한 한 줄.
추가 선택 단계
컨트롤러 약속
ES6 약속을 사용하는 사람들에게만 적용됩니다. ES2017 async / await
버전은 이미 나에게 잘 어울립니다 .
어떤 이유로, 나는 Promise.resolve()
초기화 약속을 구축 하기 위해 이름을 사용하는 것을 싫어 합니다. 거기에 무슨 일이 일어나고 있는지 분명하지 않습니다.
차라리 이해하기 쉬운 것으로 바꾸고 싶습니다.
const chain = Promise.resolve(); // Write this as an external imported variable or a global.
chain
.then(() => ...)
.then(() => ...)
이제 여러분은 이것이 chain
약속 체인의 시작 임을 알았습니다 . 여러분의 코드를 읽는 모든 사람이 그렇습니다. 그렇지 않더라도 적어도 그것이 서비스 기능의 체인이라고 가정합니다.
익스프레스 오류 처리기
Express에는 최소한 예상치 못한 오류를 캡처하는 데 사용해야하는 기본 오류 처리기가 있습니다.
router.use((err, req, res, next) => {
// Expected errors always throw Error.
// Unexpected errors will either throw unexpected stuff or crash the application.
if (Object.prototype.isPrototypeOf.call(Error.prototype, err)) {
return res.status(err.status || 500).json({ error: err.message });
}
console.error('~~~ Unexpected error exception start ~~~');
console.error(req);
console.error(err);
console.error('~~~ Unexpected error exception end ~~~');
return res.status(500).json({ error: '⁽ƈ ͡ (ुŏ̥̥̥̥םŏ̥̥̥̥) ु' });
});
또한 로그를 처리하는 더 전문적인 방법 인 대신 디버그 또는 윈스턴 과 같은 것을 사용해야합니다 console.error
.
이것이 우리가 이것을 다음과 controllerHandler
같이 연결하는 방법입니다 .
...
} catch (error) {
return res.status(500) && next(error);
}
캡처 된 오류를 Express의 오류 처리기로 리디렉션합니다.
ApiError로 오류
Error
Javascript에서 예외를 던질 때 오류를 캡슐화하는 기본 클래스로 간주됩니다. 자신의 제어 된 오류 만 추적하려는 경우에는 throw Error
및 Express 오류 처리기를에서 Error
로 변경 ApiError
하고 상태 필드에 추가하여 필요에 맞게 조정할 수도 있습니다.
export class ApiError {
constructor(message, status = 500) {
this.message = message;
this.status = status;
}
}
추가 정보
사용자 지정 예외
throw new Error('whatever')
을 사용하거나 사용하여 언제든지 사용자 지정 예외를 throw 할 수 있습니다 new Promise((resolve, reject) => reject('whatever'))
. 당신은 Promise
.
ES6 ES2017
그것은 매우 독단적 인 포인트입니다. IMO ES6 (또는 이제 공식적인 기능 세트를 갖춘 ES2017 )은 Node.js를 기반으로 한 대규모 프로젝트에서 작업하는 적절한 방법입니다.
아직 사용하고 있지 않다면 ES6 기능과 ES2017 및 Babel 트랜스 파일러를 살펴보세요.
결과
주석이나 주석이없는 완전한 코드 (이미 이전에 표시됨)입니다. 해당 섹션으로 스크롤하여이 코드와 관련된 모든 것을 확인할 수 있습니다.
router.js
const controllerHandler = (promise, params) => async (req, res, next) => {
const boundParams = params ? params(req, res, next) : [];
try {
const result = await promise(...boundParams);
return res.json(result || { message: 'OK' });
} catch (error) {
return res.status(500) && next(error);
}
};
const c = controllerHandler;
router.get('/users/:username', c(getUser, (req, res, next) => [req.params.username]));
controllers / user.js
import { serviceFunction } from service/user.js
export async function getUser(username) {
const user = await findUserByUsername();
const chat = await getChat(user);
const somethingElse = doSomethingElse(chat);
return somethingElse;
}
services / user.js
import User from '../models/User';
export function getUser(username) {
return User.find({}).exec();
}
models / User / index.js
import { validateUsername } from './validate';
const userSchema = new Schema({
username: {
type: String,
unique: true,
validate: [{ validator: validateUsername, msg: 'Invalid username' }],
},
}, { timestamps: true });
const User = mongoose.model('User', userSchema);
export default User;
models / User / validate.js
export function validateUsername(username) {
return true;
}
누구나 프로젝트를 특정 폴더로 나눌 수있는 고유 한 방법이 있습니다. 내가 사용하는 구조는
- 구성
- 로그
- 노선
- 컨트롤러
- 모델
- 서비스
- 유틸리티
- app.js / server.js / index.js (사용자가 선호하는 이름)
config 폴더에는 "생산", "개발", "테스트"와 같은 모든 개발 단계에 대한 데이터베이스 연결 설정과 같은 구성 파일이 포함되어 있습니다.
예
'use strict'
var dbsettings = {
"production": {
//your test settings
},
"test": {
},
"development": {
"database": "be",
"username": "yourname",
"password": "yourpassword",
"host": "localhost",
"connectionLimit": 100
}
}
module.exports = dbsettings
로그 폴더에는 디버깅을위한 연결 로그 오류 로그가 포함됩니다.
컨트롤러는 req 데이터와 비즈니스 로직을 검증하기위한 것입니다.
예
const service = require("../../service")
const async = require("async")
exports.techverify = (data, callback) => {
async.series([
(cb) => {
let searchObject = { accessToken: data.accessToken }
service.admin.get(searchObject, (err, result) => {
if (err || result.length == 0) {
callback(err, { message: "accessToken is invalid" })
} else {
delete data.accessToken
service.tech.update(data, { verified: true }, (err, affe, res) => {
if (!err)
callback(err, { message: "verification done" })
else
callback(err, { message: "error occured" })
})
}
})
}
])
}
모델은 DB 스키마를 정의하기위한 것입니다.
mongoDb 스키마 예
'use strict'
let mongoose = require('mongoose');
let schema = mongoose.Schema;
let user = new schema({
accesstoken: { type: String },
firstname: { type: String },
lastname: { type: String },
email: { type: String, unique: true },
image: { type: String },
phoneNo: { type: String },
gender: { type: String },
deviceType: { type: String },
password: { type: String },
regAddress: { type: String },
pincode: { type: String },
fbId: { type: String, default: 0 },
created_at: { type: Date, default: Date.now },
updated_at: { type: Date, default: Date.now },
one_time_password: { type: String },
forgot_password_token: { type: String },
is_block: { type: Boolean, default: 0 },
skin_type: { type: String },
hair_length: { type: String },
hair_type: { type: String },
credits: { type: Number, default: 0 },
invite_code: { type: String },
refered_by: { type: String },
card_details: [{
card_type: { type: String },
card_no: { type: String },
card_cv_no: { type: String },
created_at: { type: Date }
}]
});
module.exports = mongoose.model('user', user);
서비스는 데이터베이스 쿼리를 작성하는 것입니다. 컨트롤러에서 쿼리를 작성하지 마십시오.이 폴더에 쿼리를 작성하고 컨트롤러에서 호출하십시오.
몽구스를 사용하는 쿼리
'use strict'
const modelUser = require('../../models/user');
exports.insert = (data, callback) => {
console.log('mongo log for insert function', data)
new modelUser(data).save(callback)
}
exports.get = (data, callback) => {
console.log('mongo log for get function', data)
modelUser.find(data, callback)
}
exports.update = (data, updateData, callback) => {
console.log('mongo log for update function', data)
modelUser.update(data, updateData, callback);
}
exports.getWithProjection = (data, projection, callback) => {
console.log('mongo log for get function', data)
modelUser.find(data, projection, callback)
}
utils는 암호화, 암호 해독 등과 같이 프로젝트에서 일반적으로 사용되는 일반적인 유틸리티 기능입니다.
예
exports.checkPassword = (text, psypherText) => {
console.log("checkPassword executed")
console.log(text, psypherText)
return bcrypt.compareSync(text, psypherText)
}
exports.generateToken = (userEmail) => {
return jwt.sign({ unique: userEmail, timeStamp: Date.now }, config.keys.jsonwebtoken)
}
rohit salaria의 답변은 기본적으로 Java에서 사용하는 것과 동일한 앱 구조를 설명합니다.
- 컨트롤러는 Java의 컨트롤러입니다.
- 모델은 데이터 액세스 계층입니다.
- 서비스는 서비스 계층입니다.
I have a few remarks though. The first and most important one is that this is not Java. It may sound obvious but just look at your question and see that you are for looking the same development experience with the same concepts you got used in the Java world. My following remarks are just the explanation for this.
Missing DTOs. In Java they are just required, period. In a Java web application, where you store your data in a relational database and sending and receiving data to the front-end in JSON, it is natural that you convert the data to a Java object. However in a Node app everything is javascript and JSON. That is one of the strengths of the platform. With JSON being the common data format, it is not needed to write code or depend on libraries to translate between the data format of your layers.
Passing the data object directly from the request to the model. Why not? Having JSON as the common data format from the front-end to the database enables you to easily synchronize the data model of your app between all your layers. Of course you don't have to go this way, but it is sufficient most of the time, so why not use it? As for validation it is done in the model, where it belongs according to the MVC theory (, and not in the controller where laziness and pragmatism often puts it :)).
For the final thought I want to add, that this is not the best platform when it comes to project size scaling. It is nod bat at all, but Java is better in that aspect.
simple and basic rule
-
Keep components associated close to each other.
-
Divide the page into components and work
-
All the dependent components should be together
-
shared things should be kept independent of all the other components .
Finally every language is sweet. Its just that how familiar you are with the language.You can only win the battle if you familiar with you sword.
i am developing Angular2 application using NodeJS,Angular2 i'll help you with my directory structure.
`the main module`
`the sub module structure`
`keep the shared folder as a separate module`
Hope it helps :)
ReferenceURL : https://stackoverflow.com/questions/41875617/building-enterprise-app-with-node-express
'IT Share you' 카테고리의 다른 글
JS 코드가 "var a = document.querySelector ( 'a [data-a = 1]');"인 이유 (0) | 2020.12.15 |
---|---|
Java 8 Clock으로 클래스 단위 테스트 (0) | 2020.12.15 |
PhpMailer 대 SwiftMailer? (0) | 2020.12.15 |
PHP에서 슈퍼 글로벌 변수를 만드시겠습니까? (0) | 2020.12.15 |
루비를 느리게 만드는 것은 무엇입니까? (0) | 2020.12.15 |