├── app ├── database.ts ├── config.ts ├── logger.ts ├── routes.ts └── app.ts ├── data └── .gitignore ├── .dockerignore ├── index.ts ├── .prettierrc ├── .gitignore ├── test ├── bootstrap.js ├── mocha.opts ├── unit.spec.ts └── routes.spec.ts ├── Dockerfile ├── .editorconfig ├── .travis.yml ├── tsconfig.json ├── public └── swagger.yml ├── tslint.json ├── README.md └── package.json /app/database.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | require('./app/app'); -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | /.nyc_output 4 | /coverage 5 | /dist -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | const bunyan = require('bunyan'); 2 | 3 | process.env.LOG_LEVEL = `${bunyan.FATAL + 1}`; 4 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require source-map-support/register 2 | --require test/bootstrap.js 3 | --full-trace 4 | --bail -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.4 2 | 3 | WORKDIR /server 4 | 5 | COPY . /server 6 | RUN npm install 7 | 8 | EXPOSE 3000 9 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | indent_style = space 4 | indent_size = 2 5 | insert_final_newline = true 6 | 7 | [package.json] 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /app/config.ts: -------------------------------------------------------------------------------- 1 | export interface IConfig { 2 | port: number; 3 | } 4 | 5 | export const config: IConfig = { 6 | port: parseInt(process.env.NODE_PORT, 10) || 3000, 7 | }; 8 | -------------------------------------------------------------------------------- /test/unit.spec.ts: -------------------------------------------------------------------------------- 1 | // import { expect } from 'chai'; 2 | 3 | describe('SOMETHING HERE', () => { 4 | 5 | it('SHOULD DO SOMETHING', done => { 6 | 7 | done(); 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /app/logger.ts: -------------------------------------------------------------------------------- 1 | import * as bunyan from 'bunyan'; 2 | 3 | export const logger = bunyan.createLogger({ 4 | name: process.env.npm_package_name, 5 | level: (process.env.LOG_LEVEL as bunyan.LogLevelString) || 'debug', 6 | serializers: bunyan.stdSerializers 7 | }); 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | cache: 5 | directories: 6 | - "node_modules" 7 | script: 8 | - npm test 9 | - npm run lint 10 | - npm run coverage 11 | - npm run coverage:coveralls 12 | after_script: 13 | - cat ./build/coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "noImplicitAny": true, 6 | "outDir": "./dist", 7 | "sourceMap": true 8 | }, 9 | "types": ["mocha"], 10 | "include": ["./**/*.ts", "./**/*.js"], 11 | "exclude": ["node_modules", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /app/routes.ts: -------------------------------------------------------------------------------- 1 | import * as Router from 'koa-router'; 2 | 3 | const router = new Router(); 4 | 5 | /** 6 | * Base route, return a 401 7 | */ 8 | router.get('/', async ctx => ctx.status = 401); 9 | 10 | /** 11 | * Basic healthcheck 12 | */ 13 | router.get('/healthcheck', async ctx => ctx.body = 'OK'); 14 | 15 | export const routes = router.routes(); 16 | -------------------------------------------------------------------------------- /public/swagger.yml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: "A super cool endpoint for rendering a json list of tracks." 4 | version: "1.0.0" 5 | title: "YOUR PROJECT NAME" 6 | license: 7 | name: "Apache 2.0" 8 | url: "http://www.apache.org/licenses/LICENSE-2.0.html" 9 | host: "localhost:3000" 10 | basePath: "/api/v1" 11 | schemes: 12 | - "http" 13 | paths: -------------------------------------------------------------------------------- /app/app.ts: -------------------------------------------------------------------------------- 1 | import * as Koa from 'koa'; 2 | import * as koaBody from 'koa-body'; 3 | import * as cors from '@koa/cors'; 4 | 5 | const serve = require('koa-static'); 6 | const koaValidator = require('koa-async-validator'); 7 | const koaSwagger = require('koa2-swagger-ui'); 8 | const koaBunyanLogger = require('koa-bunyan-logger'); 9 | 10 | import { config } from './config'; 11 | import { routes } from './routes'; 12 | import { logger } from './logger'; 13 | 14 | const app = new Koa(); 15 | 16 | app.use(koaBody()); 17 | app.use(koaValidator()); 18 | app.use(cors()); 19 | app.use(koaBunyanLogger(logger)); 20 | app.use(koaBunyanLogger.requestLogger()); 21 | app.use(koaBunyanLogger.timeContext()); 22 | app.use(routes); 23 | app.use(serve('public')); 24 | app.use( 25 | koaSwagger({ 26 | routePrefix: '/swagger', 27 | swaggerOptions: { 28 | url: '/swagger.yml' 29 | } 30 | }) 31 | ); 32 | 33 | export const server = app.listen(config.port); 34 | 35 | console.log(`Server running on port ${config.port}`); 36 | -------------------------------------------------------------------------------- /test/routes.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { expect } from 'chai'; 3 | import { server } from './../app/app'; 4 | 5 | chai.use(require('chai-http')); 6 | 7 | describe('routes', () => { 8 | after(() => Promise.resolve(server.close())); 9 | 10 | describe(`GET /`, () => { 11 | it('should error on the default route with a 401', done => { 12 | chai 13 | .request(server) 14 | .get(`/`) 15 | .end((err, res) => { 16 | expect(res.status).to.eql(401); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | 22 | describe(`GET /healthcheck`, () => { 23 | it('should healthcheck', done => { 24 | chai 25 | .request(server) 26 | .get(`/healthcheck`) 27 | .end((err, res) => { 28 | isOk(err, res, 200, 'text/plain'); 29 | done(); 30 | }); 31 | }); 32 | }); 33 | }); 34 | 35 | const isOk = ( 36 | err: any, 37 | res: any, 38 | status: number = 200, 39 | type: string = 'application/json' 40 | ) => { 41 | expect(err).to.not.exist; 42 | expect(res.status).to.eql(status); 43 | expect(res.type).to.eql(type); 44 | }; 45 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | 2 | 3 | { 4 | "rules": { 5 | "class-name": true, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "indent": [ 11 | true, 12 | "spaces" 13 | ], 14 | "one-line": [ 15 | true, 16 | "check-open-brace", 17 | "check-whitespace" 18 | ], 19 | "no-var-keyword": true, 20 | "no-unused-variable": true, 21 | "quotemark": [ 22 | true, 23 | "single", 24 | "avoid-escape" 25 | ], 26 | "semicolon": [ 27 | true, 28 | "always", 29 | "ignore-bound-class-methods" 30 | ], 31 | "whitespace": [ 32 | true, 33 | "check-branch", 34 | "check-decl", 35 | "check-operator", 36 | "check-module", 37 | "check-separator", 38 | "check-type" 39 | ], 40 | "typedef-whitespace": [ 41 | true, 42 | { 43 | "call-signature": "nospace", 44 | "index-signature": "nospace", 45 | "parameter": "nospace", 46 | "property-declaration": "nospace", 47 | "variable-declaration": "nospace" 48 | }, 49 | { 50 | "call-signature": "onespace", 51 | "index-signature": "onespace", 52 | "parameter": "onespace", 53 | "property-declaration": "onespace", 54 | "variable-declaration": "onespace" 55 | } 56 | ], 57 | "no-internal-module": true, 58 | "no-trailing-whitespace": true, 59 | "no-null-keyword": true, 60 | "prefer-const": true, 61 | "jsdoc-format": true 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KOA2 TypeScript Starter 2 | 3 | [![Build Status](https://travis-ci.org/ddimaria/koa-typescript-starter.svg?branch=develop)](https://travis-ci.org/ddimaria/koa-typescript-starter) 4 | [![Coverage Status](https://coveralls.io/repos/github/ddimaria/koa-typescript-starter/badge.svg?branch=develop)](https://coveralls.io/github/ddimaria/koa-typescript-starter?branch=develop) 5 | 6 | A NodeJS server built with the KOA2 framework using TypeScript. 7 | 8 | Technologies Used: 9 | 10 | * [KOA2](http://koajs.com/) 11 | * [TypeScript](https://www.typescriptlang.org/) 12 | * [Travis CLI](https://travis-ci.org/) 13 | * [Coveralls](https://coveralls.io/) 14 | * [Jasmine](https://jasmine.github.io/) 15 | * [Chai](http://www.chaijs.com/) 16 | * [Istanbul/NYC](https://github.com/istanbuljs/nyc) 17 | * [Lodash](https://lodash.com/) 18 | * [Nodemon](https://nodemon.io/) 19 | * [Docker](https://www.docker.com/) 20 | * [Swagger](https://swagger.io/) 21 | * [Bunyahn](https://github.com/trentm/node-bunyan) 22 | * [Koa Bunyan Logger](https://github.com/koajs/bunyan-logger/) 23 | 24 | ## Prerequisites 25 | 26 | * Node.js (8+): recommend using [nvm](https://github.com/creationix/nvm) 27 | * Docker (if building a docker image) https://www.docker.com/docker-mac 28 | 29 | ## Installation 30 | 31 | First, clone this repo and `cd` into the main directory. Then: 32 | 33 | ```shell 34 | npm install 35 | ``` 36 | 37 | ## Development 38 | 39 | During development, the `/app` folder is being watched for changes. 40 | 41 | All changes invoke the TypeScript compiler, which restarts the app upon completion. 42 | 43 | ```shell 44 | npm run watch 45 | ``` 46 | 47 | ## Build the Server 48 | 49 | To compile the TypeScript code and place into the `/dist` folder: 50 | 51 | ```shell 52 | npm build 53 | ``` 54 | 55 | ## Code Linter 56 | 57 | A TypeScript linter has been added to keep code consistent among developers. 58 | 59 | ```shell 60 | npm run lint 61 | ``` 62 | 63 | To autofix linting errors (not all errors are auto-fixable): 64 | 65 | ```shell 66 | npm run fix 67 | ``` 68 | 69 | ## Tests and Coverage 70 | 71 | The test coverage percentage should be 90% or greater for any submitted PRs. 72 | 73 | For TDD, invoke testing by: 74 | 75 | ```shell 76 | npm test 77 | ``` 78 | 79 | For an html and text coverage report (html located in the `/coverage` folder): 80 | 81 | ```shell 82 | npm run coverage 83 | ``` 84 | 85 | ## Docker 86 | 87 | To build a container using the `dockerfile`: 88 | 89 | ```shell 90 | npm run image:build -- --no-cache 91 | ``` 92 | 93 | --- 94 | 95 | ## API 96 | 97 | For a swagger version of this documention, see http://localhost:3000/swagger (requires this server to be running). 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-typescript-starter", 3 | "version": "1.0.3", 4 | "description": "A starter/seed for KOA2/TypeScript.", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "npm run build && node ./dist/index.js", 8 | "build": "npm run lint && node_modules/typescript/bin/tsc", 9 | "watch": "nodemon --watch 'app/**/*' -e ts --exec 'ts-node' ./app/app.ts", 10 | "lint": "node_modules/tslint/bin/tslint -c tslint.json -p tsconfig.json --force", 11 | "fix": "node_modules/tslint/bin/tslint -c tslint.json -p tsconfig.json --fix --force", 12 | "image:build": "docker build --tag koa-typescript-starter .", 13 | "image:run": "docker run --rm -ti -p 3000:3000 koa-typescript-starter", 14 | "test": "NODE_ENV=test node_modules/mocha/bin/mocha -r ts-node/register ./test/**/*.spec.ts && exit 0", 15 | "coverage": "NODE_ENV=test node_modules/nyc/bin/nyc.js --reporter=html --reporter=text node_modules/mocha/bin/mocha --exit -r ts-node/register ./test/**/*.spec.ts", 16 | "coverage:coveralls": "NODE_ENV=test node_modules/nyc/bin/nyc.js --reporter=lcov --reporter=text-lcov node_modules/mocha/bin/mocha --exit -r ts-node/register ./test/**/*.spec.ts | coveralls" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/ddimaria/koa-typescript-starter.git" 21 | }, 22 | "keywords": [ 23 | "loyalty", 24 | "checkin", 25 | "petro" 26 | ], 27 | "author": "David DiMaria", 28 | "license": "ISC", 29 | "dependencies": { 30 | "@koa/cors": "^2.2.2", 31 | "koa": "^2.5.2", 32 | "koa-body": "^2.6.0", 33 | "koa-bunyan-logger": "^2.0.0", 34 | "koa-router": "^7.4.0", 35 | "koa-static": "^4.0.3", 36 | "koa2-swagger-ui": "^2.9.5", 37 | "lodash": "^4.17.10" 38 | }, 39 | "devDependencies": { 40 | "@types/bunyan": "^1.8.4", 41 | "@types/chai": "^4.1.4", 42 | "@types/chai-http": "^3.0.5", 43 | "@types/koa": "^2.0.46", 44 | "@types/koa-router": "^7.0.31", 45 | "@types/koa__cors": "^2.2.3", 46 | "@types/lodash": "^4.14.116", 47 | "@types/mocha": "^5.2.5", 48 | "chai": "^4.1.2", 49 | "chai-http": "^4.0.0", 50 | "coveralls": "^3.0.2", 51 | "koa-async-validator": "^0.1.2", 52 | "mocha": "^5.2.0", 53 | "mocha-lcov-reporter": "^1.3.0", 54 | "nodemon": "^1.18.3", 55 | "nyc": "^11.9.0", 56 | "source-map-support": "^0.5.6", 57 | "ts-node": "^6.2.0", 58 | "tslint": "^5.11.0", 59 | "typescript": "^3.0.1" 60 | }, 61 | "nyc": { 62 | "extension": [ 63 | ".ts", 64 | ".tsx" 65 | ], 66 | "excludes": [ 67 | "**/*.d.ts", 68 | "./test/**/*" 69 | ], 70 | "require": [ 71 | "ts-node/register" 72 | ], 73 | "reporter": [ 74 | "text-summary", 75 | "html", 76 | "lcov" 77 | ], 78 | "sourceMap": true, 79 | "instrument": true 80 | } 81 | } 82 | --------------------------------------------------------------------------------