오랫만에 Express를 사용할 일이 생겼다.
기억이 가물가물해서 다시 한번 복습.
mkdir my-api-node
cd my-api-node
yarn init -y
mkdir src
컴파일 결과를 삭제하기 위해 rimraf를 global로 설치한다.
npm install rimraf -g
필수 package를 설치한다.
yarn add express body-parser cors morgan
yarn add -D eslint prettier nodemon rimraf
yarn add -D babel-core babel-eslint babel-jest @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-env
yarn add -D eslint-config-prettier eslint-config-standard eslint-friendly-formatter eslint-loader eslint-plugin-import eslint-plugin-jest eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise eslint-plugin-standard
.babelrc
{
"presets": [
["@babel/env", {
"targets": {
"node": "current"
}
}]
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread"
]
}
.eslintrc.js
module.exports = {
parserOptions: {
sourceType: 'module',
},
parser: 'babel-eslint',
plugins: ['prettier'],
extends: ['eslint:recommended', 'plugin:prettier/recommended', 'plugin:jest/recommended'],
env: {
node: true,
},
ignorePatterns: ['node_modules/'],
rules: {
'prettier/prettier': 'error',
'newline-per-chained-call': 'error',
},
};
.prettierrc.js
module.exports = {
arrowParens: 'always',
orderedImports: true,
printWidth: 120,
singleQuote: true,
semi: true,
tabWidth: 2,
trailingComma: 'all',
useTabs: false,
};
nodemon.json
{
"restartable": "rs",
"ignore": [
".git",
"node_modules/**/node_modules",
"dist"
],
"verbose": true,
"execMap": {
"js": "node"
},
"runOnChangeOnly": false,
"watch": [
"src/**/*.js",
"src/**/*.graphql",
"src/**/*.gql"
],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json,graphql"
}
"scripts": {
"build": "babel src --out-dir dist",
"start": "node dist/index.js",
"restart": "rimraf dist && yarn build && yarn start",
"dev": "nodemon --exec yarn restart"
},
src/server.js
import express from 'express';
import cors from 'cors';
import { json, urlencoded } from 'body-parser';
import morgan from 'morgan';
const app = express();
app.disable('x-powered-by');
app.use(cors());
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(morgan('dev'));
export const start = () => {
app.listen(5000, () => {
console.log('server is on 5000');
});
};
src/index.js
import { start } from './server';
start();
간단한 get, post api를 만들어보자.
import express from 'express';
import cors from 'cors';
import { json, urlencoded } from 'body-parser';
import morgan from 'morgan';
const app = express();
app.disable('x-powered-by');
app.use(cors());
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(morgan('dev'));
app.get('/', (req, res) => {
res.send({ message: 'hello' });
});
app.post('/', (req, res) => {
console.log(req.body);
res.send(req.body);
});
export const start = () => {
app.listen(5000, () => {
console.log('server is on 5000');
});
};
import express from 'express';
import cors from 'cors';
import { json, urlencoded } from 'body-parser';
import morgan from 'morgan';
const app = express();
app.disable('x-powered-by');
app.use(cors());
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(morgan('dev'));
const globalLog = (req, res, next) => {
console.log('global logging');
next();
};
app.use(globalLog);
const log = (req, res, next) => {
console.log('logging');
next();
};
app.get('/', log, (req, res) => {
res.send({ message: 'hello' });
});
app.get('/data', [log, log, log], (req, res) => {
res.send({ message: 'hello' });
});
app.post('/', (req, res) => {
console.log(req.body);
res.send(req.body);
});
export const start = () => {
app.listen(5000, () => {
console.log('server is on 5000');
});
};
middleware를 위와 같은 방식으로 사용할 수 있다.
src/resources/item 폴더를 생성하고 item.router.js 파일을 생성한다.
import { Router } from 'express';
const controller = (req, res) => {
res.send({ message: 'hello' });
};
const router = Router();
// prettier-ignore
// /api/item
router.route("/")
.get(controller)
.post(controller);
// prettier-ignore
// /api/item/:id
router.route("/:id")
.get(controller)
.put(controller)
.delete(controller);
export default router;
임시로 controller를 생성하고, 모든 router가 controller를 호출하도록 설정했다.
생성한 router를 server.js에서 사용한다.
import express from 'express';
import cors from 'cors';
import { json, urlencoded } from 'body-parser';
import morgan from 'morgan';
import itemRouter from './resources/item/item.router';
const app = express();
app.disable('x-powered-by');
app.use(cors());
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(morgan('dev'));
app.use('/api/item', itemRouter);
export const start = () => {
app.listen(5000, () => {
console.log('server is on 5000');
});
};
이렇게 설정하면 /api/item
로 호출되는 end point는 itemRouter를 이용하게 된다.
테스트를 추가해보자.
jest를 설치한다.
yarn add -D jest
package.json 파일에 scripts를 추가한다.
"scripts": {
"build": "babel src --out-dir dist",
"start": "node dist/index.js",
"restart": "rimraf dist && yarn build && yarn start",
"dev": "nodemon --exec yarn restart",
"test": "NODE_ENV=testing jest --forceExit --detectOpenHandles --silent",
"test-routes": "yarn test -t router"
},
테스트를 추가한다.
/src/resources/item/__tests__
경로에 item.router.spec.js 파일을 생성한다.
import router from '../item.router';
describe('item router', () => {
test('has crud routes', () => {
const routes = [
{ path: '/', method: 'get' },
{ path: '/', method: 'post' },
{ path: '/:id', method: 'get' },
{ path: '/:id', method: 'put' },
{ path: '/:id', method: 'delete' },
];
routes.forEach((route) => {
const match = router.stack.find((s) => s.route.path === route.path && s.route.methods[route.method]);
expect(match).toBeTruthy();
});
});
});
router.stack은 다음과 같이 생겼다.
[
Layer {
handle: [Function: bound dispatch],
name: 'bound dispatch',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i { fast_star: false, fast_slash: false },
route: Route {
path: '/',
stack: [ [Layer], [Layer] ],
methods: { get: true, post: true }
}
},
Layer {
handle: [Function: bound dispatch],
name: 'bound dispatch',
params: undefined,
path: undefined,
keys: [ { name: 'id', optional: false, offset: 1 } ],
regexp: /^\/(?:([^\/]+?))\/?$/i { fast_star: false, fast_slash: false },
route: Route {
path: '/:id',
stack: [ [Layer], [Layer], [Layer] ],
methods: { get: true, put: true, delete: true }
}
}
]
router.stack
의 정보를 이용하여 router test를 작성할 수 있다.
오랫만에 Node 코드를 보니 새롭네.
예전에 너무 설렁설렁했었나봐. 좀 더 자세히 보자.
꾸준히! 열심히!