PostgreSQL로 Express API 백엔드 프로젝트를 설정하는 방법
게시 됨: 2022-03-10TDD(Test-Driven Development) 접근 방식과 CI(지속적 통합) 작업을 설정하여 Travis CI 및 AppVeyor에서 테스트를 자동으로 실행하고 코드 품질 및 적용 범위 보고를 완료합니다. 컨트롤러, 모델(PostgreSQL 포함), 오류 처리 및 비동기식 Express 미들웨어에 대해 학습합니다. 마지막으로 Heroku에서 자동 배포를 구성하여 CI/CD 파이프라인을 완료합니다.
많은 것 같지만 이 튜토리얼은 어느 정도의 복잡성이 있는 백엔드 프로젝트를 직접 시도할 준비가 되어 있고 실제 프로젝트에서 모든 부분이 어떻게 조화를 이루는지 여전히 혼란스러울 수 있는 초보자를 대상으로 합니다. .
압도적이지 않고 견고하며 합리적인 시간 내에 완료할 수 있는 섹션으로 나뉩니다.
시작하기
첫 번째 단계는 프로젝트에 대한 새 디렉터리를 만들고 새 노드 프로젝트를 시작하는 것입니다. 이 튜토리얼을 계속하려면 노드가 필요합니다. 설치되어 있지 않다면 공식 웹사이트로 이동하여 다운로드하여 설치한 후 계속 진행하십시오.
이 프로젝트의 패키지 관리자로 원사를 사용할 것입니다. 여기에 특정 운영 체제에 대한 설치 지침이 있습니다. 원하는 경우 npm을 자유롭게 사용하십시오.
터미널을 열고 새 디렉터리를 만들고 Node.js 프로젝트를 시작합니다.
# create a new directory mkdir express-api-template # change to the newly-created directory cd express-api-template # initialize a new Node.js project npm init
다음 질문에 답하여 package.json 파일을 생성하십시오. 이 파일에는 프로젝트에 대한 정보가 들어 있습니다. 이러한 정보의 예에는 사용하는 종속성, 프로젝트 시작 명령 등이 포함됩니다.
이제 선택한 편집기에서 프로젝트 폴더를 열 수 있습니다. Visual Studio 코드를 사용합니다. 여러분의 삶을 더 쉽게 만들어주는 수많은 플러그인이 포함된 무료 IDE이며 모든 주요 플랫폼에서 사용할 수 있습니다. 공식 웹사이트에서 다운로드할 수 있습니다.
프로젝트 폴더에 다음 파일을 만듭니다.
- README.md
- .editorconfig
다음은 EditorConfig 웹사이트에서 .editorconfig가 수행하는 작업에 대한 설명입니다. (솔로 작업하시는 분들은 아마 필요없을 텐데, 나쁠건 없으니 그냥 두겠습니다.)
"EditorConfig는 다양한 편집기와 IDE에서 동일한 프로젝트에서 작업하는 여러 개발자를 위해 일관된 코딩 스타일을 유지하는 데 도움이 됩니다."
.editorconfig
를 열고 다음 코드를 붙여넣습니다.
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
[*]
는 프로젝트의 모든 파일에 해당 규칙을 적용하려는 것을 의미합니다. 두 개의 공백과 UTF-8
문자 집합의 들여쓰기 크기를 원합니다. 또한 후행 공백을 잘라내고 파일에 마지막 빈 줄을 삽입하려고 합니다.
README.md 를 열고 프로젝트 이름을 첫 번째 수준 요소로 추가합니다.
# Express API template
바로 버전 관리를 추가해 보겠습니다.
# initialize the project folder as a git repository git init
.gitignore 파일을 만들고 다음 줄을 입력합니다.
node_modules/ yarn-error.log .env .nyc_output coverage build/
이것들은 우리가 추적하고 싶지 않은 모든 파일과 폴더입니다. 아직 프로젝트에는 없지만 진행하면서 보게 될 것입니다.
이 때 다음과 같은 폴더 구조가 있어야 합니다.
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
이것이 변경 사항을 커밋하고 GitHub에 푸시하기에 좋은 포인트라고 생각합니다.
새로운 익스프레스 프로젝트 시작하기
Express는 웹 애플리케이션을 구축하기 위한 Node.js 프레임워크입니다. 공식 홈페이지에 따르면
Node.js를 위한 빠르고 객관적이며 미니멀한 웹 프레임워크입니다.
Node.js를 위한 다른 훌륭한 웹 애플리케이션 프레임워크가 있지만 Express는 이 글을 쓰는 시점에 47,000개 이상의 GitHub 별을 보유한 매우 인기가 있습니다.
이 기사에서는 Express를 구성하는 모든 부분에 대해 많은 논의를 하지 않을 것입니다. 그 토론을 위해 Jamie의 시리즈를 확인하는 것이 좋습니다. 첫 번째 부분이 여기 있고 두 번째 부분이 여기에 있습니다.
Express를 설치하고 새 Express 프로젝트를 시작하십시오. Express 서버를 처음부터 수동으로 설정할 수 있지만 우리의 삶을 더 쉽게 만들기 위해 express-generator를 사용하여 앱 스켈레톤을 설정할 것입니다.
# install the express generator globally yarn global add express-generator # install express yarn add express # generate the express project in the current folder express -f
-f
플래그는 Express가 현재 디렉토리에 프로젝트를 생성하도록 합니다.
이제 몇 가지 집 청소 작업을 수행합니다.
- index/users.js 파일을 삭제합니다.
-
public/
및views/
폴더를 삭제합니다. - bin/www 파일의 이름을 bin/www.js 로 바꿉니다.
-
yarn remove jade
명령으로jade
를 제거합니다. -
src/
라는 새 폴더를 만들고 그 안에 다음을 이동합니다. 1. app.js 파일 2.bin/
폴더 3.routes/
폴더 내부. - package.json 을 열고
start
스크립트를 아래와 같이 업데이트합니다.
"start": "node ./src/bin/www"
이 시점에서 프로젝트 폴더 구조는 아래와 같습니다. VS Code가 발생한 파일 변경 사항을 강조 표시하는 방법을 볼 수 있습니다.
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
src/app.js 를 열고 내용을 아래 코드로 바꿉니다.
var logger = require('morgan'); var express = require('express'); var cookieParser = require('cookie-parser'); var indexRouter = require('./routes/index'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); module.exports = app;
일부 라이브러리를 요구한 후 Express가 indexRouter
를 사용하여 /v1
에 오는 모든 요청을 처리하도록 지시합니다.
route/index.js 의 내용을 아래 코드로 바꿉니다.
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { return res.status(200).json({ message: 'Welcome to Express API template' }); }); module.exports = router;
Express를 잡고 라우터를 만들고 /
경로를 제공하여 상태 코드 200
과 JSON 메시지를 반환합니다.
아래 명령으로 앱을 시작합니다.
# start the app yarn start
모든 것을 올바르게 설정했다면 터미널에 $ node ./src/bin/www
만 보일 것입니다.
브라우저에서 https://localhost:3000/v1
을 방문하십시오. 다음 메시지가 표시되어야 합니다.
{ "message": "Welcome to Express API template" }
이것은 변경 사항을 커밋하기에 좋은 지점입니다.
- 내 저장소의 해당 분기는 01-install-express입니다.
코드를 ES6
으로 변환
express-generator
에 의해 생성된 코드는 ES5
에 있지만 이 기사에서는 모든 코드를 ES6
구문으로 작성합니다. 따라서 기존 코드를 ES6
으로 변환해 보겠습니다.
route/index.js 의 내용을 아래 코드로 바꿉니다.
import express from 'express'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: 'Welcome to Express API template' }) ); export default indexRouter;
위에서 본 것과 동일한 코드이지만 /
route 핸들러에 import 문과 화살표 기능이 있습니다.
src/app.js 의 내용을 아래 코드로 바꿉니다.
import logger from 'morgan'; import express from 'express'; import cookieParser from 'cookie-parser'; import indexRouter from './routes/index'; const app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); export default app;
이제 src/bin/www.js 의 내용을 살펴보겠습니다. 점진적으로 구축해 나가겠습니다. src/bin/www.js
의 내용을 삭제하고 아래 코드 블록에 붙여넣습니다.
#!/usr/bin/env node /** * Module dependencies. */ import debug from 'debug'; import http from 'http'; import app from '../app'; /** * Normalize a port into a number, string, or false. */ const normalizePort = val => { const port = parseInt(val, 10); if (Number.isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }; /** * Get port from environment and store in Express. */ const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ const server = http.createServer(app); // next code block goes here
이 코드는 사용자 지정 포트가 환경 변수에 지정되었는지 확인합니다. 아무 것도 설정하지 않으면 normalizePort 에 의해 문자열이나 숫자로 normalizePort
된 후 앱 인스턴스에 기본 포트 값 3000
이 설정됩니다. 그런 다음 서버는 http
모듈에서 생성되며 app
을 콜백 함수로 사용합니다.
#!/usr/bin/env node
행은 이 파일을 실행할 때 노드를 지정하므로 선택 사항입니다. 그러나 src/bin/www.js 파일의 1행에 있는지 확인하거나 완전히 제거하십시오.
오류 처리 기능을 살펴보겠습니다. 서버가 생성된 줄 뒤에 이 코드 블록을 복사하여 붙여넣습니다.
/** * Event listener for HTTP server "error" event. */ const onError = error => { if (error.syscall !== 'listen') { throw error; } const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': alert(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': alert(`${bind} is already in use`); process.exit(1); break; default: throw error; } }; /** * Event listener for HTTP server "listening" event. */ const onListening = () => { const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; debug(`Listening on ${bind}`); }; /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening);
onError
함수는 http 서버의 오류를 수신하고 적절한 오류 메시지를 표시합니다. onListening
함수는 단순히 서버가 콘솔에서 수신 대기 중인 포트를 출력합니다. 마지막으로 서버는 지정된 주소와 포트에서 들어오는 요청을 수신합니다.
이 시점에서 기존 코드는 모두 ES6
구문에 있습니다. 서버를 중지하고( Ctrl + C 사용) yarn start
를 실행합니다. SyntaxError: Invalid or unexpected token
오류가 발생합니다. 이것은 Node(작성 당시)가 코드에서 사용한 구문 중 일부를 지원하지 않기 때문에 발생합니다.
이제 다음 섹션에서 수정하겠습니다.
개발 종속성 구성: babel
, nodemon
, eslint
및 prettier
프로젝트의 이 단계에서 필요한 대부분의 스크립트를 설정할 시간입니다.
아래 명령어로 필요한 라이브러리를 설치합니다. 모든 것을 복사하여 터미널에 붙여넣기만 하면 됩니다. 주석 행은 건너뜁니다.
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
그러면 나열된 모든 babel 스크립트가 개발 종속성으로 설치됩니다. package.json 파일을 확인하면 devDependencies
섹션이 표시되어야 합니다. 설치된 모든 스크립트가 거기에 나열됩니다.
우리가 사용하는 babel 스크립트는 다음과 같습니다.
@babel/cli | babel 을 사용하기 위한 필수 설치입니다. 터미널에서 Babel을 사용할 수 있도록 하며 ./node_modules/.bin/babel 로 사용할 수 있습니다. |
@babel/core | 핵심 Babel 기능. 이것은 필수 설치입니다. |
@babel/node | 이것은 babel 사전 설정 및 플러그인으로 컴파일하는 추가 이점과 함께 Node.js CLI와 정확히 동일하게 작동합니다. 이것은 nodemon 과 함께 사용하는 데 필요합니다. |
@babel/plugin-transform-runtime | 이렇게 하면 컴파일된 출력에서 중복을 방지하는 데 도움이 됩니다. |
@babel/preset-env | 코드 변환을 수행하는 플러그인 모음입니다. |
@babel/register | 이것은 파일을 즉석에서 컴파일하고 테스트 중에 요구 사항으로 지정됩니다. |
@babel/runtime | 이것은 @babel/plugin-transform-runtime 과 함께 작동합니다. |
프로젝트 루트에 .babelrc 라는 파일을 만들고 다음 코드를 추가합니다.
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
노드몬을 설치 nodemon
# install nodemon yarn add nodemon --dev
nodemon
은 프로젝트 소스 코드를 모니터링하고 변경 사항이 관찰될 때마다 서버를 자동으로 다시 시작하는 라이브러리입니다.
프로젝트 루트에 nodemon.json 이라는 파일을 만들고 아래 코드를 추가합니다.
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
watch
키는 nodemon
에게 변경 사항을 감시할 파일과 폴더를 알려줍니다. 따라서 이러한 파일이 변경될 때마다 nodemon은 서버를 다시 시작합니다. ignore
키는 파일이 변경 사항을 감시하지 않도록 지시합니다.
이제 package.json 파일의 scripts
섹션을 다음과 같이 업데이트하십시오.
# build the content of the src folder "prestart": "babel ./src --out-dir build" # start server from the build folder "start": "node ./build/bin/www" # start server in development mode "startdev": "nodemon --exec babel-node ./src/bin/www"
-
prestart
스크립트는src/
폴더의 내용을 빌드하고 이를build/
폴더에 넣습니다.yarn start
명령을 실행하면 이 스크립트가start
스크립트보다 먼저 실행됩니다. - 이제
start
스크립트는 이전에 제공했던src/
폴더 대신build/
폴더의 내용을 제공합니다. 이것은 프로덕션에서 파일을 제공할 때 사용할 스크립트입니다. 실제로 Heroku와 같은 서비스는 배포할 때 이 스크립트를 자동으로 실행합니다. -
yarn startdev
는 개발 중에 서버를 시작하는 데 사용됩니다. 지금부터 우리는 앱을 개발할 때 이 스크립트를 사용할 것입니다. 이제 일반node
대신babel-node
를 사용하여 앱을 실행하고 있습니다.--exec
플래그는babel-node
가src/
폴더를 제공하도록 합니다.start
스크립트의 경우build/
폴더의 파일이 ES5로 컴파일되었으므로node
를 사용합니다.
yarn startdev
를 실행하고 https://localhost:3000/v1을 방문하세요. 서버가 다시 가동되어야 합니다.
이 섹션의 마지막 단계는 ESLint
및 prettier
를 구성하는 것입니다. ESLint는 구문 규칙을 적용하는 데 도움이 되는 반면 prettier는 가독성을 위해 코드 형식을 적절하게 지정하는 데 도움이 됩니다.
아래 명령으로 둘 다 추가하십시오. 우리 서버가 실행되는 터미널을 관찰하면서 별도의 터미널에서 이것을 실행해야 합니다. 서버가 다시 시작되는 것을 볼 수 있습니다. 이는 package.json 파일의 변경 사항을 모니터링하기 때문입니다.
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
이제 프로젝트 root
에 .eslintrc.json 파일을 만들고 아래 코드를 추가합니다.
{ "env": { "browser": true, "es6": true, "node": true, "mocha": true }, "extends": ["airbnb-base"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { "indent": ["warn", 2], "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"], "no-console": 1, "comma-dangle": [0], "arrow-parens": [0], "object-curly-spacing": ["warn", "always"], "array-bracket-spacing": ["warn", "always"], "import/prefer-default-export": [0] } }
이 파일은 대부분 eslint
가 코드를 검사할 몇 가지 규칙을 정의합니다. Airbnb에서 사용하는 스타일 규칙을 확장하고 있음을 알 수 있습니다.
"rules"
섹션에서 eslint
가 특정 위반 사항을 만났을 때 경고 또는 오류를 표시할지 여부를 정의합니다. 예를 들어, 2개의 공백을 사용하지 않는 들여쓰기에 대해 터미널에 경고 메시지를 표시합니다. 값 [0]
은 규칙을 해제합니다. 즉, 해당 규칙을 위반할 경우 경고나 오류가 발생하지 않습니다.
.prettierrc 라는 파일을 만들고 아래 코드를 추가합니다.
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
탭 너비를 2
로 설정하고 애플리케이션 전체에 작은 따옴표를 사용합니다. 더 많은 스타일링 옵션은 더 예쁜 가이드를 확인하세요.
이제 다음 스크립트를 package.json 에 추가하십시오.
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
yarn lint
를 실행하십시오. 콘솔에 여러 오류와 경고가 표시되어야 합니다.
pretty
명령은 코드를 멋지게 만듭니다. postpretty
명령은 직후에 실행됩니다. --fix
플래그가 추가된 lint
명령을 실행합니다. 이 플래그는 ESLint
가 일반적인 린트 문제를 자동으로 수정하도록 지시합니다. 이런 식으로 lint
명령에 신경 쓰지 않고 yarn pretty
명령을 주로 실행합니다.
yarn pretty
실행하십시오. bin/www.js 파일에 alert
가 있다는 경고가 두 개뿐임을 알 수 있습니다.
이 시점에서 우리의 프로젝트 구조는 다음과 같습니다.
EXPRESS-API-TEMPLATE ├── build ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── nodemon.json ├── package.json ├── README.md └── yarn.lock
프로젝트 루트에 yarn-error.log
라는 추가 파일이 있음을 알 수 있습니다. .gitignore
파일에 추가합니다. 변경 사항을 커밋합니다.
- 내 저장소의 이 시점에서 해당 분기는 02-dev-dependencies입니다.
.env 파일의 설정 및 환경 변수
거의 모든 프로젝트에서 앱 전체에서 사용할 설정(예: AWS 보안 키)을 저장할 위치가 필요합니다. 이러한 설정을 환경 변수로 저장합니다. 이렇게 하면 눈에 띄지 않고 필요에 따라 애플리케이션 내에서 사용할 수 있습니다.
모든 환경 변수를 읽을 수 있는 settings.js 파일이 있는 것을 좋아합니다. 그런 다음 내 앱 내 어디에서나 설정 파일을 참조할 수 있습니다. 이 파일의 이름은 원하는 대로 지정할 수 있지만 이러한 파일의 이름을 settings.js 또는 config.js 로 지정하는 데에는 어느 정도 합의가 있습니다.
환경 변수의 경우 .env
파일에 보관하고 거기에서 settings
파일로 읽어들입니다.
프로젝트 루트에 .env 파일을 만들고 아래 줄을 입력합니다.
TEST_ENV_VARIABLE="Environment variable is coming across"
우리 프로젝트에서 환경 변수를 읽을 수 있도록 .env
파일을 읽고 내부에 정의된 환경 변수에 대한 액세스를 제공하는 멋진 라이브러리 dotenv
가 있습니다. 설치합시다.
# install dotenv yarn add dotenv
nodemon
이 감시하는 파일 목록에 .env 파일을 추가합니다.
이제 src/
폴더 안에 settings.js 파일을 만들고 아래 코드를 추가합니다.
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
dotenv
패키지를 가져오고 config 메소드를 호출합니다. 그런 다음 .env
파일에 설정한 testEnvironmentVariable
을 내보냅니다.
src/routes/index.js 를 열고 코드를 아래 코드로 바꿉니다.
import express from 'express'; import { testEnvironmentVariable } from '../settings'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: testEnvironmentVariable })); export default indexRouter;
여기서 우리가 한 유일한 변경 사항은 settings
파일에서 testEnvironmentVariable
을 가져오고 /
경로의 요청에 대한 반환 메시지로 사용한다는 것입니다.
https://localhost:3000/v1을 방문하면 아래와 같이 메시지가 표시됩니다.
{ "message": "Environment variable is coming across." }
그리고 그게 다야. 이제부터 원하는 만큼 환경 변수를 추가하고 settings.js 파일에서 내보낼 수 있습니다.
이것은 코드를 커밋하기에 좋은 지점입니다. 코드를 예쁘게 꾸미고 린트하는 것을 잊지 마십시오.
- 내 저장소의 해당 분기는 03-env-variables입니다.
첫 번째 테스트 작성하기
이제 테스트를 앱에 통합할 때입니다. 개발자에게 코드에 대한 확신을 주는 것 중 하나는 테스트입니다. 웹에서 테스트 주도 개발(TDD)을 설교하는 수많은 기사를 보셨을 것입니다. 코드에 어느 정도 테스트가 필요하다는 점은 아무리 강조해도 지나치지 않습니다. TDD는 Express.js로 작업할 때 매우 쉽게 따라할 수 있습니다.
테스트에서 API 엔드포인트를 호출하고 반환되는 내용이 예상한 것인지 확인합니다.
필요한 종속성을 설치합니다.
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
이러한 각 라이브러리는 테스트에서 수행할 고유한 역할이 있습니다.
mocha | 테스트 러너 |
chai | 주장을 하기 위해 사용 |
nyc | 테스트 커버리지 보고서 수집 |
sinon-chai | 차이의 주장을 확장 |
supertest | API 엔드포인트에 대한 HTTP 호출을 수행하는 데 사용 |
coveralls | Coveralls.io에 테스트 커버리지 업로드용 |
프로젝트 루트에 새 test/
폴더를 만듭니다. 이 폴더 안에 두 개의 파일을 만듭니다.
- 테스트/설정.js
- 테스트/인덱스.test.js
Mocha는 자동으로 test/
폴더를 찾습니다.
test/setup.js 를 열고 아래 코드를 붙여넣습니다. 이것은 테스트 파일에 필요한 모든 가져오기를 구성하는 데 도움이 되는 도우미 파일일 뿐입니다.
import supertest from 'supertest'; import chai from 'chai'; import sinonChai from 'sinon-chai'; import app from '../src/app'; chai.use(sinonChai); export const { expect } = chai; export const server = supertest.agent(app); export const BASE_URL = '/v1';
이것은 설정 파일과 비슷하지만 테스트용입니다. 이렇게 하면 각 테스트 파일 내부의 모든 것을 초기화할 필요가 없습니다. 그래서 우리는 필요한 패키지를 가져오고 초기화한 것을 내보냅니다. 그런 다음 이를 필요로 하는 파일로 가져올 수 있습니다.
index.test.js 를 열고 다음 테스트 코드를 붙여넣습니다.
import { expect, server, BASE_URL } from './setup'; describe('Index page test', () => { it('gets base url', done => { server .get(`${BASE_URL}/`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.message).to.equal( 'Environment variable is coming across.' ); done(); }); }); });
여기서 우리는 기본 엔드포인트 /
가져오도록 요청합니다 res.
body
개체에는 Environment variable is coming across.
값이 있는 message
키가 있습니다.
describe
, it
패턴에 익숙하지 않다면 Mocha의 "시작하기" 문서를 빠르게 살펴보시기 바랍니다.
package.json 의 scripts
섹션에 테스트 명령을 추가합니다.
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
이 스크립트는 nyc
로 테스트를 실행하고 세 가지 종류의 커버리지 보고서를 생성합니다. HTML 보고서, coverage/
폴더로 출력됨; 터미널로 출력되는 텍스트 보고서와 .nyc_output/
폴더로 출력되는 lcov 보고서.
이제 yarn test
를 실행하십시오. 아래 사진과 같이 터미널에 텍스트 보고서가 표시되어야 합니다.
두 개의 추가 폴더가 생성됩니다.
-
.nyc_output/
-
coverage/
.gitignore
내부를 살펴보면 우리가 이미 둘 다 무시하고 있음을 알 수 있습니다. 브라우저에서 coverage/index.html
을 열고 각 파일에 대한 테스트 보고서를 볼 것을 권장합니다.
이것은 변경 사항을 커밋하기에 좋은 지점입니다.
- 내 저장소의 해당 분기는 04-first-test입니다.
지속적인 통합(CD) 및 배지: Travis, 작업복, Code Climate, AppVeyor
이제 CI/CD(지속적 통합 및 배포) 도구를 구성할 차례입니다. travis-ci
, coveralls
, AppVeyor
및 codeclimate
와 같은 공통 서비스를 구성하고 README 파일에 배지를 추가합니다.
시작하자.
트래비스 CI
Travis CI는 커밋을 GitHub(최근에는 Bitbucket)에 푸시할 때마다 그리고 풀 리퀘스트를 생성할 때마다 자동으로 테스트를 실행하는 도구입니다. 이것은 우리의 새로운 코드가 우리의 테스트를 깨뜨렸는지 보여줌으로써 pull 요청을 할 때 가장 유용합니다.
- travis-ci.com 또는 travis-ci.org를 방문하여 계정이 없으면 만드십시오. GitHub 계정으로 가입해야 합니다.
- 프로필 사진 옆에 있는 드롭다운 화살표 위로 마우스를 가져간 다음
settings
을 클릭합니다. -
Repositories
탭Manage repositories on Github
으로 리디렉션합니다. - GitHub 페이지에서
Repository access
까지 아래로 스크롤하고 리포지토리Only select repositories
옆에 있는 확인란을 클릭합니다. -
Select repositories
선택 드롭다운을 클릭하고express-api-template
저장소를 찾습니다.travis-ci
에 추가하려는 저장소 목록에 추가하려면 클릭하십시오. -
Approve and install
를 클릭하고 다시travis-ci
로 리디렉션될 때까지 기다립니다. - 리포지토리 페이지 상단에서 리포지토리 이름 옆에 있는
build unknown
아이콘을 클릭합니다. 상태 이미지 모달의 형식 드롭다운에서 마크다운을 선택합니다. - 결과 코드를 복사하여 README.md 파일에 붙여넣습니다.
- 프로젝트 페이지에서
More options
>Settings
을 클릭합니다.Environment Variables
섹션에서TEST_ENV_VARIABLE
환경 변수를 추가합니다. 값을 입력할 때"Environment variable is coming across."
와 같이 큰따옴표로 묶어야 합니다. - 프로젝트 루트에 .travis.yml 파일을 만들고 아래 코드를 붙여넣습니다(코드 기후 섹션에서
CC_TEST_REPORTER_ID
값을 설정합니다).
language: node_js env: global: - CC_TEST_REPORTER_ID=get-this-from-code-climate-repo-page matrix: include: - node_js: '12' cache: directories: [node_modules] install: yarn after_success: yarn coverage before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: - yarn test after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESUL
먼저 Travis에 Node.js로 테스트를 실행한 다음 CC_TEST_REPORTER_ID
전역 환경 변수를 설정하도록 지시합니다(코드 기후 섹션에서 이에 대해 설명함). matrix
섹션에서 Travis에게 Node.js v12로 테스트를 실행하도록 지시합니다. 또한 매번 다시 생성할 필요가 없도록 node_modules/
디렉토리를 캐시하기를 원합니다.
우리는 yarn install
의 줄임말인 yarn
명령을 사용하여 종속성을 설치합니다. before_script
및 after_script
명령은 적용 결과를 codeclimate
에 업로드하는 데 사용됩니다. 곧 codeclimate
를 구성할 것입니다. yarn test
가 성공적으로 실행된 후 Coveralls.io에 적용 보고서를 업로드할 yarn coverage
도 실행하려고 합니다.
작업복
작업복은 쉬운 시각화를 위해 테스트 범위 데이터를 업로드합니다. 적용 범위 폴더에서 로컬 시스템의 테스트 적용 범위를 볼 수 있지만 Coveralls는 로컬 시스템 외부에서 사용할 수 있습니다.
- Coveralls.io를 방문하여 로그인하거나 Github 계정으로 가입하십시오.
- 화면 왼쪽 위로 마우스를 가져가면 탐색 메뉴가 나타납니다.
ADD REPOS
를 클릭합니다. -
express-api-template
repo를 검색하고 왼쪽에 있는 토글 버튼을 사용하여 적용 범위를 켭니다. 찾을 수 없으면 오른쪽 상단 모서리에 있는SYNC REPOS
를 클릭하고 다시 시도하십시오. PRO 계정이 없는 한 저장소는 공개되어야 합니다. - 상세내역을 클릭하시면 리포지토리 상세페이지로 이동합니다.
- 프로젝트 루트에 .coveralls.yml 파일을 만들고 아래 코드를 입력합니다.
repo_token
을 얻으려면 repo 세부 정보를 클릭하십시오. 해당 페이지에서 쉽게 찾을 수 있습니다.repo_token
에 대한 브라우저 검색을 수행할 수 있습니다.
repo_token: get-this-from-repo-settings-on-coveralls.io
이 토큰은 Coveralls의 리포지토리에 커버리지 데이터를 매핑합니다. 이제 package.json 파일의 scripts
섹션에 coverage
명령을 추가합니다.
"coverage": "nyc report --reporter=text-lcov | coveralls"
이 명령은 .nyc_output
폴더의 적용 범위 보고서를 coveralls.io에 업로드합니다. 인터넷 연결을 켜고 다음을 실행합니다.
yarn coverage
이렇게 하면 기존 적용 보고서를 작업복에 업로드해야 합니다. 전체 보고서를 보려면 작업복의 리포지토리 페이지를 새로고침하세요.
세부 정보 페이지에서 아래로 스크롤하여 BADGE YOUR REPO
섹션을 찾습니다. EMBED
드롭다운을 클릭하고 마크다운 코드를 복사하여 README 파일에 붙여넣습니다.
코드 기후
Code Climate는 코드 품질을 측정하는 데 도움이 되는 도구입니다. 일부 정의된 패턴에 대해 코드를 확인하여 유지 관리 메트릭을 보여줍니다. 불필요한 반복 및 깊이 중첩된 for 루프와 같은 것을 감지합니다. 또한 Coveralls.io와 마찬가지로 테스트 커버리지 데이터를 수집합니다.
- codeclimate.com을 방문하여 'GitHub에 가입'을 클릭합니다. 이미 계정이 있는 경우 로그인합니다.
- 대시보드에서
Add a repository
를 클릭합니다. - 목록에서
express-api-template
repo를 찾고Add Repo
를 클릭하십시오. - 빌드가 완료될 때까지 기다렸다가 리포지토리 대시보드로 리디렉션합니다.
-
Codebase Summary
에서Test Coverage
를 클릭하십시오.Test coverage
메뉴에서TEST REPORTER ID
를 복사하여 .travis.yml 에CC_TEST_REPORTER_ID
값으로 붙여넣습니다. - 여전히 같은 페이지에 있는 왼쪽 탐색 메뉴의
EXTRAS
아래에서 배지를 클릭합니다. 마크다운 형식으로maintainability
및test coverage
배지를 복사하여 README.md 파일에 붙여넣습니다.
유지 관리 검사를 구성하는 방법에는 두 가지가 있습니다. 모든 리포지토리에 적용되는 기본 설정이 있지만 원하는 경우 프로젝트 루트에 .codeclimate.yml 파일을 제공할 수 있습니다. 저장소 설정 페이지의 Maintainability
탭에서 찾을 수 있는 기본 설정을 사용하겠습니다. 나는 당신이 최소한 살펴보기를 권장합니다. 여전히 자신만의 설정을 구성하려는 경우 이 가이드에서 필요한 모든 정보를 제공합니다.
앱베이어
AppVeyor와 Travis CI는 모두 자동화된 테스트 러너입니다. 주요 차이점은 travis-ci는 Linux 환경에서 테스트를 실행하는 반면 AppVeyor는 Windows 환경에서 테스트를 실행한다는 것입니다. 이 섹션은 AppVeyor를 시작하는 방법을 보여주기 위해 포함되었습니다.
- AppVeyor를 방문하여 로그인하거나 가입하십시오.
- 다음 페이지에서
NEW PROJECT
를 클릭하십시오. - 리포지토리 목록에서
express-api-template
리포지토리를 찾습니다. 그 위로 마우스를 가져간 다음ADD
를 클릭합니다. -
Settings
탭을 클릭합니다. 왼쪽 탐색에서Environment
을 클릭합니다.TEST_ENV_VARIABLE
및 해당 값을 추가합니다. 페이지 하단의 '저장'을 클릭합니다. - 프로젝트 루트에 appveyor.yml 파일을 생성하고 아래 코드를 붙여넣습니다.
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
이 코드는 Node.js v12를 사용하여 테스트를 실행하도록 AppVeyor에 지시합니다. 그런 다음 yarn
명령으로 프로젝트 종속성을 설치합니다. test_script
는 테스트를 실행할 명령을 지정합니다. 마지막 줄은 AppVeyor에 빌드 폴더를 만들지 말라고 지시합니다.
Settings
탭을 클릭합니다. 왼쪽 탐색 메뉴에서 배지를 클릭합니다. 마크다운 코드를 복사하여 README.md 파일에 붙여넣습니다.
코드를 커밋하고 GitHub에 푸시합니다. 지시에 따라 모든 작업을 수행했다면 모든 테스트를 통과해야 하며 아래와 같이 반짝이는 새 배지가 표시되어야 합니다. Travis 및 AppVeyor에서 환경 변수를 설정했는지 다시 확인하십시오.
지금이 변경 사항을 커밋할 좋은 시간입니다.
- 내 저장소의 해당 지점은 05-ci입니다.
컨트롤러 추가
현재 src/routes/index.js 내부의 루트 URL /v1
에 대한 GET
요청을 처리하고 있습니다. 이것은 예상대로 작동하며 아무런 문제가 없습니다. 그러나 응용 프로그램이 성장함에 따라 모든 것을 깔끔하게 유지하려고 합니다. 문제를 분리하기를 원합니다. 요청을 처리하는 코드와 클라이언트로 다시 보낼 응답을 생성하는 코드를 명확하게 분리하기를 원합니다. 이를 달성하기 위해 controllers
를 작성합니다. 컨트롤러는 단순히 특정 URL을 통해 들어오는 요청을 처리하는 기능입니다.
시작하려면 src/
폴더 안에 controllers/
폴더를 만드세요. 내부 controllers
는 index.js 및 home.js 라는 두 개의 파일을 생성합니다. index.js 내에서 함수를 내보냅니다. home.js 의 이름은 원하는 대로 지정할 수 있지만 일반적으로 컨트롤러가 제어하는 이름을 따서 컨트롤러의 이름을 지정하려고 합니다. 예를 들어 앱의 사용자와 관련된 모든 기능을 보관하는 usersController.js 파일이 있을 수 있습니다.
src/controllers/home.js 를 열고 아래 코드를 입력합니다.
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
/
라우트에 대한 요청을 처리하는 함수만 이동했음을 알 수 있습니다.
src/controllers/index.js 를 열고 아래 코드를 입력합니다.
// export everything from home.js export * from './home';
home.js 파일에서 모든 것을 내보냅니다. 이렇게 하면 import { indexPage } from '../controllers';
src/routes/index.js 를 열고 거기에 있는 코드를 아래 코드로 바꿉니다.
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
여기서 유일한 변경 사항은 /
경로에 대한 요청을 처리하는 기능을 제공했다는 것입니다.
첫 번째 컨트롤러를 성공적으로 작성했습니다. 여기에서 필요에 따라 더 많은 파일과 기능을 추가하는 문제입니다.
몇 가지 경로와 컨트롤러를 더 추가하여 앱을 사용해보세요. 정보 페이지에 대한 경로와 컨트롤러를 추가할 수 있습니다. 그러나 테스트를 업데이트하는 것을 잊지 마십시오.
yarn test
를 실행하여 아무 것도 손상되지 않았는지 확인합니다. 당신의 시험은 통과합니까? 멋지네요.
이것은 변경 사항을 커밋하기에 좋은 지점입니다.
- 내 저장소의 해당 분기는 06-controllers입니다.
PostgreSQL
데이터베이스 연결 및 모델 작성
컨트롤러는 현재 하드 코딩된 텍스트 메시지를 반환합니다. 실제 앱에서는 종종 데이터베이스에서 정보를 저장하고 검색해야 합니다. 이 섹션에서는 앱을 PostgreSQL 데이터베이스에 연결합니다.
데이터베이스를 사용하여 간단한 문자 메시지의 저장 및 검색을 구현합니다. 데이터베이스 설정에는 두 가지 옵션이 있습니다. 클라우드 서버에서 프로비저닝하거나 로컬에서 자체적으로 설정할 수 있습니다.
클라우드 서버에서 데이터베이스를 프로비저닝하는 것이 좋습니다. ElephantSQL에는 이 튜토리얼에 충분한 20MB의 무료 스토리지를 제공하는 무료 플랜이 있습니다. 사이트를 방문하여 Get a managed database today
를 클릭하십시오. Create an account (if you don't have one) and follow the instructions to create a free plan. Take note of the URL on the database details page. We'll be needing it soon.
If you would rather set up a database locally, you should visit the PostgreSQL and PgAdmin sites for further instructions.
Once we have a database set up, we need to find a way to allow our Express app to communicate with our database. Node.js by default doesn't support reading and writing to PostgreSQL
database, so we'll be using an excellent library, appropriately named, node-postgres.
node-postgres
executes SQL
queries in node and returns the result as an object, from which we can grab items from the rows key.
Let's connect node-postgres
to our application.
# install node-postgres yarn add pg
Open settings.js and add the line below:
export const connectionString = process.env.CONNECTION_STRING;
Open your .env
file and add the CONNECTION_STRING
variable. This is the connection string we'll be using to establish a connection to our database. The general form of the connection string is shown below.
CONNECTION_STRING="postgresql://dbuser:dbpassword@localhost:5432/dbname"
If you're using elephantSQL you should copy the URL from the database details page.
Inside your /src
folder, create a new folder called models/
. Inside this folder, create two files:
- pool.js
- model.js
Open pools.js and paste the following code:
import { Pool } from 'pg'; import dotenv from 'dotenv'; import { connectionString } from '../settings'; dotenv.config(); export const pool = new Pool({ connectionString });
First, we import the Pool
and dotenv
from the pg
and dotenv
packages, and then import the settings we created for our postgres database before initializing dotenv
. We establish a connection to our database with the Pool
object. In node-postgres
, every query is executed by a client. A Pool is a collection of clients for communicating with the database.
To create the connection, the pool constructor takes a config object. You can read more about all the possible configurations here. It also accepts a single connection string, which I will use here.
Open model.js and paste the following code:
import { pool } from './pool'; class Model { constructor(table) { this.pool = pool; this.table = table; this.pool.on('error', (err, client) => `Error, ${err}, on idle client${client}`); } async select(columns, clause) { let query = `SELECT ${columns} FROM ${this.table}`; if (clause) query += clause; return this.pool.query(query); } } export default Model;
We create a model class whose constructor accepts the database table we wish to operate on. We'll be using a single pool for all our models.
We then create a select
method which we will use to retrieve items from our database. This method accepts the columns we want to retrieve and a clause, such as a WHERE
clause. It returns the result of the query, which is a Promise
. Remember we said earlier that every query is executed by a client, but here we execute the query with pool. This is because, when we use pool.query
, node-postgres
executes the query using the first available idle client.
The query you write is entirely up to you, provided it is a valid SQL
statement that can be executed by a Postgres engine.
The next step is to actually create an API endpoint to utilize our newly connected database. Before we do that, I'd like us to create some utility functions. The goal is for us to have a way to perform common database operations from the command line.
Create a folder, utils/
inside the src/
folder. Create three files inside this folder:
- queries.js
- queryFunctions.js
- runQuery.js
We're going to create functions to create a table in our database, insert seed data in the table, and to delete the table.
query.js 를 열고 다음 코드를 붙여넣습니다.
export const createMessageTable = ` DROP TABLE IF EXISTS messages; CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, name VARCHAR DEFAULT '', message VARCHAR NOT NULL ) `; export const insertMessages = ` INSERT INTO messages(name, message) VALUES ('chidimo', 'first message'), ('orji', 'second message') `; export const dropMessagesTable = 'DROP TABLE messages';
이 파일에서 세 개의 SQL 쿼리 문자열을 정의합니다. 첫 번째 쿼리는 messages
테이블을 삭제하고 다시 만듭니다. 두 번째 쿼리는 messages
테이블에 두 개의 행을 삽입합니다. 여기에 항목을 더 추가할 수 있습니다. 마지막 쿼리는 messages
테이블을 삭제/삭제합니다.
queryFunctions.js 를 열고 다음 코드를 붙여넣습니다.
import { pool } from '../models/pool'; import { insertMessages, dropMessagesTable, createMessageTable, } from './queries'; export const executeQueryArray = async arr => new Promise(resolve => { const stop = arr.length; arr.forEach(async (q, index) => { await pool.query(q); if (index + 1 === stop) resolve(); }); }); export const dropTables = () => executeQueryArray([ dropMessagesTable ]); export const createTables = () => executeQueryArray([ createMessageTable ]); export const insertIntoTables = () => executeQueryArray([ insertMessages ]);
여기서 앞서 정의한 쿼리를 실행하는 함수를 만듭니다. executeQueryArray
함수는 쿼리 배열을 실행하고 각 쿼리가 루프 내에서 완료될 때까지 기다립니다. (그러나 프로덕션 코드에서는 그런 일을 하지 마십시오). 그런 다음 목록의 마지막 쿼리를 실행한 후에만 약속을 해결합니다. 배열을 사용하는 이유는 데이터베이스의 테이블 수가 증가함에 따라 그러한 쿼리의 수가 증가하기 때문입니다.
runQuery.js 를 열고 다음 코드를 붙여넣습니다.
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
여기에서 테이블을 생성하고 테이블에 메시지를 삽입하는 함수를 실행합니다. 이 파일을 실행하기 위해 package.json 의 scripts
섹션에 명령을 추가해 보겠습니다.
"runQuery": "babel-node ./src/utils/runQuery"
이제 실행:
yarn runQuery
데이터베이스를 검사하면 messages
테이블이 생성되었고 메시지가 테이블에 삽입되었음을 알 수 있습니다.
ElephantSQL을 사용하는 경우 데이터베이스 세부 정보 페이지의 왼쪽 탐색 메뉴에서 BROWSER
를 클릭합니다. messages
테이블을 선택하고 Execute
을 클릭하십시오. query.js 파일의 메시지가 표시되어야 합니다.
컨트롤러를 만들고 데이터베이스의 메시지를 표시하는 경로를 만들어 보겠습니다.
새 컨트롤러 파일 src/controllers/messages.js 를 만들고 다음 코드를 붙여넣습니다.
import Model from '../models/model'; const messagesModel = new Model('messages'); export const messagesPage = async (req, res) => { try { const data = await messagesModel.select('name, message'); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
Model
클래스를 가져오고 해당 모델의 새 인스턴스를 만듭니다. 이것은 데이터베이스의 messages
테이블을 나타냅니다. 그런 다음 모델의 select
메소드를 사용하여 데이터베이스를 쿼리합니다. 우리가 얻은 데이터( name
및 message
)는 응답에서 JSON으로 전송됩니다.
messagesPage
컨트롤러를 async
함수로 정의합니다. node-postgres
쿼리는 약속을 반환하므로 해당 쿼리의 결과를 await
립니다. 쿼리 중에 오류가 발생하면 이를 잡아서 사용자에게 스택을 표시합니다. 오류 처리 방법을 결정해야 합니다.
메시지 가져오기 엔드포인트를 src/routes/index.js 에 추가하고 가져오기 라인을 업데이트합니다.
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
https://localhost:3000/v1/messages를 방문하면 아래와 같이 메시지가 표시되는 것을 볼 수 있습니다.
이제 테스트 파일을 업데이트하겠습니다. TDD를 수행할 때 일반적으로 테스트를 통과하게 하는 코드를 구현하기 전에 테스트를 작성합니다. 저는 아직 데이터베이스 설정 작업을 하고 있기 때문에 여기서 반대의 접근 방식을 취하고 있습니다.
test/
폴더에 hooks.js 라는 새 파일을 만들고 아래 코드를 입력합니다.
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
테스트가 시작되면 Mocha는 이 파일을 찾아 테스트 파일을 실행하기 전에 실행합니다. 데이터베이스를 생성하고 일부 항목을 삽입하기 위해 before
후크를 실행합니다. 그런 다음 테스트 파일이 실행됩니다. 테스트가 after
Mocha는 데이터베이스를 삭제하는 후크를 실행합니다. 이렇게 하면 테스트를 실행할 때마다 데이터베이스에서 깨끗하고 새로운 레코드로 수행할 수 있습니다.
새 테스트 파일 test/messages.test.js 를 만들고 아래 코드를 추가합니다.
import { expect, server, BASE_URL } from './setup'; describe('Messages', () => { it('get messages page', done => { server .get(`${BASE_URL}/messages`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('name'); expect(m).to.have.property('message'); }); done(); }); }); });
우리는 /messages
에 대한 호출의 결과가 배열이라고 주장합니다. 각 메시지 개체에 대해 name
과 message
속성이 있다고 주장합니다.
이 섹션의 마지막 단계는 CI 파일을 업데이트하는 것입니다.
.travis.yml 파일에 다음 섹션을 추가합니다.
services: - postgresql addons: postgresql: "10" apt: packages: - postgresql-10 - postgresql-client-10 before_install: - sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf - sudo /etc/init.d/postgresql restart
이것은 Travis가 테스트를 실행하기 전에 PostgreSQL 10 데이터베이스를 가동하도록 지시합니다.
before_script
섹션의 첫 번째 항목으로 데이터베이스를 생성하는 명령을 추가합니다.
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
Travis에서 CONNECTION_STRING
환경 변수를 만들고 아래 값을 사용합니다.
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
.appveyor.yml 파일에 다음 섹션을 추가합니다.
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
appveyor에 연결 문자열 환경 변수를 추가합니다. 아래 줄을 사용하십시오.
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
이제 변경 사항을 커밋하고 GitHub에 푸시합니다. 테스트는 Travis CI와 AppVeyor 모두에서 통과해야 합니다.
- 내 저장소의 해당 분기는 07-connect-postgres입니다.
참고 : 결국 모든 것이 잘 작동하기를 바랍니다. 하지만 어떤 이유로든 문제가 발생할 경우를 대비하여 언제든지 저장소에서 내 코드를 확인할 수 있습니다!
이제 데이터베이스에 메시지를 추가하는 방법을 살펴보겠습니다. 이 단계에서는 POST
요청을 URL로 보내는 방법이 필요합니다. Postman을 사용하여 POST
요청을 보낼 것입니다.
TDD 경로로 이동하고 우리가 달성할 것으로 예상되는 것을 반영하도록 테스트를 업데이트합시다.
test/message.test.js 를 열고 아래 테스트 케이스를 추가하십시오.
it('posts messages', done => { const data = { name: 'some name', message: 'new message' }; server .post(`${BASE_URL}/messages`) .send(data) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('id'); expect(m).to.have.property('name', data.name); expect(m).to.have.property('message', data.message); }); done(); }); });
이 테스트는 /v1/messages
엔드포인트에 POST 요청을 하고 배열이 반환될 것으로 예상합니다. 또한 배열에서 id
, name
및 message
속성을 확인합니다.
테스트를 실행하여 이 경우가 실패하는지 확인하십시오. 이제 수정해 보겠습니다.
포스트 요청을 보내기 위해 우리는 서버의 포스트 메소드를 사용합니다. 우리는 또한 우리가 삽입하고 싶은 이름과 메시지를 보냅니다. 응답은 속성 id
와 쿼리를 구성하는 기타 정보가 포함된 배열이 될 것으로 예상합니다. id
는 레코드가 데이터베이스에 삽입되었다는 증거입니다.
src/models/model.js 를 열고 insert
메소드를 추가하십시오:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
이것은 데이터베이스에 메시지를 삽입할 수 있는 방법입니다. 항목을 삽입한 후 id
, name
및 message
를 반환합니다.
src/controllers/messages.js 를 열고 아래 컨트롤러를 추가합니다.
export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
요청 본문을 구조화하여 이름과 메시지를 가져옵니다. 그런 다음 값을 사용하여 SQL 쿼리 문자열을 만든 다음 모델의 insertWithReturn
메서드로 실행합니다.
/src/routes/index.js 에 아래 POST
엔드포인트를 추가하고 가져오기 라인을 업데이트하십시오.
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
테스트를 실행하여 통과하는지 확인하십시오.
Postman을 열고 messages
끝점에 POST
요청을 보냅니다. 테스트를 방금 실행했다면, messages
테이블을 재생성하기 위해 yarn query
를 실행하는 것을 잊지 마십시오.
yarn query
변경 사항을 커밋하고 GitHub에 푸시합니다. 테스트는 Travis와 AppVeyor 모두에서 통과해야 합니다. 테스트 범위가 몇 점 떨어지지만 괜찮습니다.
- 내 저장소의 해당 분기는 08-post-to-db입니다.
미들웨어
Express에 대한 논의는 미들웨어에 대한 이야기 없이는 완전하지 않습니다. Express 설명서에서는 미들웨어를 다음과 같이 설명합니다.
“[...] 애플리케이션의 요청-응답 주기에서 요청 객체(req
), 응답 객체(res
) 및 다음 미들웨어 기능에 액세스할 수 있는 기능. 다음 미들웨어 기능은 일반적으로next
라는 변수로 표시됩니다."
미들웨어는 인증, 요청 본문 수정 등과 같은 여러 기능을 수행할 수 있습니다. 미들웨어 사용에 대한 Express 설명서를 참조하십시오.
요청 본문을 수정하는 간단한 미들웨어를 작성할 것입니다. 미들웨어는 데이터베이스에 저장되기 전에 들어오는 메시지에 SAYS:
라는 단어를 추가합니다.
시작하기 전에 달성하고자 하는 것을 반영하도록 테스트를 수정해 보겠습니다.
test/messages.test.js 를 열고 posts message
테스트 케이스의 마지막 예상 라인을 수정합니다.
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
우리는 SAYS:
문자열이 메시지에 추가되었다고 주장합니다. 테스트를 실행하여 이 테스트 케이스가 실패하는지 확인하십시오.
이제 테스트를 통과하도록 코드를 작성해 보겠습니다.
src/
폴더 안에 새로운 middleware/
폴더를 생성합니다. 이 폴더 안에 두 개의 파일을 만듭니다.
- 미들웨어.js
- index.js
middleware.js 에 아래 코드를 입력하세요.
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
여기에서 요청 본문의 메시지에 SAYS:
문자열을 추가합니다. 그런 다음 요청-응답 체인의 다음 함수로 실행을 전달하기 위해 next()
함수를 호출해야 합니다. 모든 미들웨어는 요청-응답 주기에서 다음 미들웨어로 실행을 전달하기 위해 next
함수를 호출해야 합니다.
index.js 에 아래 코드를 입력하세요.
# export everything from the middleware file export * from './middleware';
이것은 /middleware.js 파일에 있는 미들웨어를 내보냅니다. 지금은 modifyMessage
미들웨어만 있습니다.
src/routes/index.js 를 열고 포스트 메시지 요청-응답 체인에 미들웨어를 추가합니다.
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
modifyMessage
함수가 addMessage
함수보다 먼저 온다는 것을 알 수 있습니다. modifyMessage
미들웨어에서 next
를 호출하여 addMessage
함수를 호출합니다. 실험으로 modifyMessage
중간에 next()
줄을 주석 처리하고 요청이 중단되는 것을 지켜보십시오.
Postman을 열고 새 메시지를 작성하십시오. 추가된 문자열이 표시되어야 합니다.
이것은 변경 사항을 커밋하기에 좋은 지점입니다.
- 내 저장소의 해당 분기는 09-미들웨어입니다.
오류 처리 및 비동기식 미들웨어
모든 응용 프로그램에서 오류는 불가피합니다. 개발자가 해야 할 일은 가능한 한 우아하게 오류를 처리하는 방법입니다.
익스프레스에서:
" 오류 처리 는 Express가 동기식 및 비동기식으로 발생하는 오류를 포착하고 처리하는 방법을 나타냅니다.
동기 함수만 작성했다면 Express가 이미 오류 처리를 훌륭하게 처리하고 있으므로 오류 처리에 대해 크게 걱정할 필요가 없습니다. 문서에 따르면:
"라우트 핸들러 및 미들웨어 내부의 동기 코드에서 발생하는 오류는 추가 작업이 필요하지 않습니다."
그러나 비동기 라우터 핸들러와 미들웨어를 작성하기 시작하면 몇 가지 오류 처리를 수행해야 합니다.
modifyMessage
미들웨어는 동기 함수입니다. 해당 기능에서 오류가 발생하면 Express가 잘 처리합니다. 비동기식 미들웨어에서 오류를 처리하는 방법을 살펴보겠습니다.
메시지를 만들기 전에 이 URL https://picsum.photos/id/0/info
를 사용하여 Lorem Picsum API에서 사진을 가져오고 싶다고 가정해 보겠습니다. 이것은 성공하거나 실패할 수 있는 비동기 작업이며 처리해야 할 사례를 제시합니다.
Axios를 설치하여 시작하십시오.
# install axios yarn add axios
src/middleware/middleware.js 를 열고 아래 기능을 추가하십시오.
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
이 async
함수에서 API 호출을 await
(실제로 반환된 데이터는 필요하지 않음) 나중에 요청 체인의 next
함수를 호출합니다. 요청이 실패하면 오류를 포착하고 next
으로 전달합니다. Express가 이 오류를 확인하면 체인의 다른 모든 미들웨어를 건너뜁니다. next(err)
을 호출하지 않으면 요청이 중단됩니다. err
없이 next()
만 호출하면 요청이 아무 일도 일어나지 않은 것처럼 진행되고 오류가 포착되지 않습니다.
이 함수를 가져와서 게시 메시지 경로의 미들웨어 체인에 추가합니다.
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
src/app.js 를 열고 export default app
라인 바로 앞에 아래 코드를 추가합니다.
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
이것은 우리의 오류 처리기입니다. Express 오류 처리 문서에 따르면:
"[...] 오류 처리 함수에는 3개가 아닌 4개의 인수가 있습니다: (err, req, res, next)
."
이 오류 처리기는 모든 app.use()
호출 후에 마지막에 와야 합니다. 오류가 발생하면 상태 코드가 400
인 스택 추적을 반환합니다. 오류로 원하는 모든 작업을 수행할 수 있습니다. 기록하거나 어딘가에 보내고 싶을 수 있습니다.
이것은 변경 사항을 커밋하기에 좋은 곳입니다.
- 내 저장소의 해당 분기는 10-async-middleware입니다.
Heroku에 배포
- 시작하려면 https://www.heroku.com/으로 이동하여 로그인하거나 등록하십시오.
- 여기에서 Heroku CLI를 다운로드하여 설치합니다.
- 프로젝트 폴더에서 터미널을 열어 명령을 실행합니다.
# login to heroku on command line heroku login
그러면 브라우저 창이 열리고 Heroku 계정에 로그인하라는 메시지가 표시됩니다.
터미널에 로그인하여 Heroku 계정에 대한 액세스 권한을 부여하고 다음을 실행하여 새 heroku 앱을 만듭니다.
#app name is up to you heroku create app-name
그러면 Heroku에서 앱이 생성되고 두 개의 URL이 반환됩니다.
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
오른쪽의 URL을 복사하고 아래 명령어를 실행합니다. Heroku가 이미 원격 URL을 추가했음을 알 수 있으므로 이 단계는 선택 사항입니다.
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
사이드 터미널을 열고 아래 명령어를 실행합니다. 이것은 이미지와 같이 실시간으로 앱 로그를 보여줍니다.
# see process logs heroku logs --tail
다음 세 가지 명령을 실행하여 필요한 환경 변수를 설정합니다.
heroku config:set TEST_ENV_VARIABLE="Environment variable is coming across." heroku config:set CONNECTION_STRING=your-db-connection-string-here. heroku config:set NPM_CONFIG_PRODUCTION=false
스크립트에서 다음을 설정했음을 기억하십시오.
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
앱을 시작하려면 babel이 개발 종속성에만 존재하기 때문에 prestart
단계에서 babel을 사용하여 ES5로 컴파일해야 합니다. NPM_CONFIG_PRODUCTION
도 설치할 수 있도록 false
로 설정해야 합니다.
모든 것이 올바르게 설정되었는지 확인하려면 아래 명령을 실행하십시오. 앱 페이지의 settings
탭을 방문하여 Reveal Config Vars
표시를 클릭할 수도 있습니다.
# check configuration variables heroku config
이제 git push heroku
를 실행하십시오.
앱을 열려면 다음을 실행하세요.
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
저와 같이 개발 및 프로덕션 모두에 동일한 PostgresSQL 데이터베이스를 사용하는 경우 테스트를 실행할 때마다 데이터베이스가 삭제된다는 것을 알 수 있습니다. 다시 만들려면 다음 명령 중 하나를 실행할 수 있습니다.
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
Travis를 사용한 지속적 배포(CD)
이제 CD(지속적 배포)를 추가하여 CI/CD 흐름을 완료하겠습니다. 모든 성공적인 테스트 실행 후에 Travis에서 배포할 것입니다.
첫 번째 단계는 Travis CI를 설치하는 것입니다. (여기에서 설치 지침을 찾을 수 있습니다.) Travis CI를 성공적으로 설치한 후 아래 명령을 실행하여 로그인합니다. (이 작업은 프로젝트 저장소에서 수행해야 합니다.)
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
프로젝트가 travis-ci.org에서 호스팅되는 경우 --pro
플래그를 제거하십시오. GitHub 토큰을 얻으려면 계정의 개발자 설정 페이지를 방문하여 토큰을 생성하십시오. 이는 귀하의 계정이 2FA로 보안된 경우에만 적용됩니다.
.travis.yml 을 열고 배포 섹션을 추가합니다.
deploy: provider: heroku app: master: app-name
여기에서 Heroku에 배포할 것임을 지정합니다. 앱 하위 섹션은 저장소의 master
분기를 Heroku의 app-name
앱에 배포하도록 지정합니다. 다른 앱에 다른 분기를 배포할 수 있습니다. 사용 가능한 옵션에 대한 자세한 내용은 여기에서 읽을 수 있습니다.
아래 명령을 실행하여 Heroku API 키를 암호화하고 배포 섹션에 추가합니다.
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
그러면 배포 섹션에 아래 하위 섹션이 추가됩니다.
api_key: secure: very-long-encrypted-api-key-string
이제 변경 사항을 커밋하고 로그를 모니터링하면서 GitHub에 푸시합니다. Travis 테스트가 완료되는 즉시 빌드가 트리거되는 것을 볼 수 있습니다. 이런 식으로 테스트에 실패하면 변경 사항이 배포되지 않습니다. 마찬가지로 빌드가 실패하면 전체 테스트 실행이 실패합니다. 이것으로 CI/CD 흐름이 완료됩니다.
- 내 저장소의 해당 분기는 11-cd입니다.
결론
여기까지 왔다면 "엄지손가락!"이라고 말합니다. 이 자습서에서는 새 Express 프로젝트를 성공적으로 설정했습니다. 계속해서 개발 종속성과 CI(지속적 통합)를 구성했습니다. 그런 다음 테스트를 통해 완료한 API 엔드포인트에 대한 요청을 처리하는 비동기 함수를 작성했습니다. 그런 다음 오류 처리에 대해 간략하게 살펴보았습니다. 마지막으로 프로젝트를 Heroku에 배포하고 지속적인 배포를 구성했습니다.
이제 다음 백엔드 프로젝트를 위한 템플릿이 생겼습니다. 우리는 당신이 시작할 수 있을 만큼만 했을 뿐이지만 당신은 계속하는 법을 계속 배워야 합니다. Express.js 문서도 확인하십시오. PostgreSQL
대신 MongoDB
를 사용하려는 경우 정확히 수행하는 템플릿이 있습니다. 설정을 확인하실 수 있습니다. 몇 가지 차이점만 있습니다.
자원
- "MongoDB를 사용하여 Express API 백엔드 만들기", Orji Chidi Matthew, GitHub
- "미들웨어 연결을 위한 짧은 안내서", Stephen Sugden
- "Express API 템플릿", GitHub
- "AppVeyor 대 Travis CI", StackShare
- "The Heroku CLI", Heroku 개발자 센터
- "Heroku 배포", Travis CI
- "미들웨어 사용", Express.js
- "오류 처리", Express.js
- "시작하기", 모카
-
nyc
(GitHub) - ElephantSQL
- 우편 집배원
- 표현하다
- 트래비스 CI
- 코드 기후
- PostgreSQL
- pgAdmin