├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .nvmrc ├── README.md ├── gulpfile.js ├── package.json └── src ├── controllers ├── index.js └── testCtrl.js ├── index.js ├── lib ├── logger.js └── utils.js └── models └── Model.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-0"], 3 | "plugins": [ 4 | "transform-async-to-generator", 5 | "transform-es2015-arrow-functions", 6 | "transform-es2015-block-scoped-functions", 7 | "transform-es2015-block-scoping", 8 | "transform-es2015-classes", 9 | "transform-es2015-computed-properties", 10 | "transform-es2015-destructuring", 11 | "transform-es2015-for-of", 12 | "transform-es2015-function-name", 13 | "transform-es2015-literals", 14 | "transform-es2015-modules-commonjs", 15 | "transform-es2015-object-super", 16 | "transform-es2015-parameters", 17 | "transform-es2015-shorthand-properties", 18 | "transform-es2015-spread", 19 | "transform-es2015-sticky-regex", 20 | "transform-es2015-template-literals", 21 | "transform-es2015-typeof-symbol", 22 | "transform-es2015-unicode-regex" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | indent_style = tab 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": false, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "plugins": [ 9 | "babel" 10 | ], 11 | "rules": { 12 | // ES6 13 | "babel/arrow-parens": 2, 14 | "arrow-spacing": [ 2, { "before": true, "after": true } ], 15 | "constructor-super": 2, 16 | "no-class-assign": 2, 17 | "no-this-before-super": 2, 18 | "no-var": 2, 19 | "babel/object-shorthand": [ 2, "always" ], 20 | "prefer-const": 2, 21 | "prefer-spread": 2, 22 | 23 | // Variables 24 | "init-declarations": [ 2, "always" ], 25 | "newline-after-var": 2, 26 | "no-const-assign": 2, 27 | "no-shadow": 2, 28 | "no-shadow-restricted-names": 2, 29 | "no-undef": 2, 30 | "no-undef-init": 2, 31 | "no-unused-vars": 2, 32 | "no-use-before-define": 2, 33 | "one-var": [ 2, "never" ], 34 | 35 | // Possible errors 36 | "comma-dangle": [ 2, "always-multiline" ], 37 | "no-alert": 1, 38 | "no-cond-assign": [ 2, "except-parens" ], 39 | "no-console": 0, 40 | "no-constant-condition": 1, 41 | "no-debugger": 1, 42 | "no-dupe-keys": 2, 43 | "no-duplicate-case": 2, 44 | "no-empty": 2, 45 | "no-empty-label": 2, 46 | "no-ex-assign": 2, 47 | "no-extra-boolean-cast": 2, 48 | "no-extra-semi": 2, 49 | "no-func-assign": 2, 50 | "no-inner-declarations": 2, 51 | "no-invalid-regexp": 2, 52 | "no-irregular-whitespace": 2, 53 | "no-iterator": 2, 54 | "no-native-reassign": 2, 55 | "no-obj-calls": 2, 56 | "no-proto": 2, 57 | "no-return-assign": 2, 58 | "no-sparse-arrays": 2, 59 | "no-unreachable": 2, 60 | "radix": 2, 61 | "semi": [ 2, "always" ], 62 | "use-isnan": 2, 63 | 64 | // Best practices 65 | "callback-return": 0, 66 | "eqeqeq": 1, 67 | "handle-callback-err": 2, 68 | "babel/new-cap": 2, 69 | "no-array-constructor": 2, 70 | "no-caller": 2, 71 | "no-eval": 2, 72 | "no-extra-bind": 2, 73 | "no-implied-eval": 2, 74 | "no-label-var": 2, 75 | "no-lone-blocks": 2, 76 | "no-loop-func": 1, 77 | "no-nested-ternary": 2, 78 | "no-new": 2, 79 | "no-new-func": 2, 80 | "no-new-object": 2, 81 | "no-new-wrappers": 2, 82 | "no-path-concat": 1, 83 | "no-return-assign": 2, 84 | "no-throw-literal": 2, 85 | "no-unused-expressions": 2, 86 | "no-with": 2, 87 | 88 | // Style 89 | "array-bracket-spacing": [ 2, "always" ], 90 | "brace-style": [ 2, "stroustrup", { "allowSingleLine": true } ], 91 | "camelcase": 2, 92 | "comma-spacing": [ 2, { "before": false, "after": true } ], 93 | "computed-property-spacing": [ 2, "never" ], 94 | "consistent-return": 0, 95 | "curly": 2, 96 | "dot-notation": 2, 97 | "eol-last": 2, 98 | "func-names": 0, 99 | "babel/generator-star-spacing": 1, 100 | "indent": [2, "tab", { "SwitchCase": 1 }], 101 | "key-spacing": [ 2, { "beforeColon": false, "afterColon": true } ], 102 | "new-parens": 2, 103 | "no-extra-parens": 0, 104 | "no-mixed-requires": 0, 105 | "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], 106 | "no-multi-spaces" : 2, 107 | "no-multi-str": 2, 108 | "no-multiple-empty-lines": [ 2, { "max": 2 } ], 109 | "no-sequences": 2, 110 | "no-spaced-func": 2, 111 | "no-trailing-spaces": 2, 112 | "no-underscore-dangle": 0, 113 | "no-unneeded-ternary": 2, 114 | "babel/object-curly-spacing": [ 2, "always" ], 115 | "operator-linebreak": [ 2, "after" ], 116 | "padded-blocks": [ 2, "never" ], 117 | "quotes": [ 2, "single" ], 118 | "semi-spacing": [ 2, { "before": false, "after": true } ], 119 | "space-after-keywords": 2, 120 | "space-before-blocks": 2, 121 | //"space-before-function-paren": 0, 122 | "space-in-parens": [ 2, "never" ], 123 | "space-infix-ops": [ 2, { "int32Hint": false } ], 124 | "space-return-throw-case": 2, 125 | "space-unary-ops": [ 2, { "words": true, "nonwords": false } ], 126 | "strict": [ 2, "never" ] 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v5.1.0 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koa2 2 | Example using `koa@2.0.0` and `koa-router@7.0.0` 3 | 4 | 1. `npm i` 5 | 1. `gulp` 6 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict, no-console */ 2 | 'use strict'; 3 | 4 | require('shelljs/global'); 5 | require('shelljs').config.fatal = true; 6 | 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | const respawn = require('respawn'); 10 | const program = require('commander'); 11 | const runSequence = require('run-sequence'); 12 | const gulp = require('gulp'); 13 | const gutil = require('gulp-util'); 14 | const plumber = require('gulp-plumber'); 15 | const eslint = require('gulp-eslint'); 16 | const cache = require('gulp-cached'); 17 | const del = require('del'); 18 | const babel = require('gulp-babel'); 19 | const sourcemaps = require('gulp-sourcemaps'); 20 | 21 | program.option('-d, --debug', 'Debug mode on'); 22 | program.parse(process.argv); 23 | 24 | const env = process.env; 25 | 26 | env.NODE_PATH = env.NODE_PATH || path.resolve(__dirname, 'dist'); 27 | env.NODE_ENV = env.NODE_ENV || 'development'; 28 | 29 | const paths = { 30 | src: 'src', 31 | dist: 'dist', 32 | sourceRoot: path.join(__dirname, 'src'), 33 | }; 34 | 35 | let babelOptions = null; 36 | 37 | // read babel options from .babelrc file 38 | try { 39 | babelOptions = JSON.parse(fs.readFileSync('./.babelrc', 'utf8')); 40 | } 41 | catch (ex) { 42 | throw new SyntaxError('Error while parsing .babelrc file.'); 43 | } 44 | 45 | function transpile (src, dest) { 46 | console.log(gutil.colors.cyan('⚒ Transpiling...')); 47 | 48 | return gulp.src(src) 49 | .pipe(plumber()) 50 | .pipe(cache('transpile')) 51 | .pipe(sourcemaps.init()) 52 | .pipe(babel(babelOptions)) 53 | .pipe(sourcemaps.write('.', { sourceRoot: paths.sourceRoot })) 54 | .pipe(gulp.dest(dest)); 55 | } 56 | 57 | function lint (src) { 58 | console.log(gutil.colors.cyan('⚒ Linting...')); 59 | 60 | return gulp.src(src) 61 | .pipe(cache('lint')) 62 | .pipe(eslint({ useEslintrc: true })) 63 | .pipe(eslint.format()) 64 | .pipe(eslint.failAfterError()); 65 | } 66 | 67 | gulp.task('transpile', () => transpile(`${paths.src}/**/*.js`, paths.dist)); 68 | 69 | gulp.task('lint', () => lint([ `${paths.src}/**/*.js`, 'gulpfile.js' ])); 70 | 71 | gulp.task('clean', (done) => del([ paths.dist ], done)); 72 | 73 | gulp.task('default', (done) => { 74 | const command = [ 'node', '--es_staging', '--harmony_proxies' ]; 75 | 76 | // if debug flag was specified, run node in debug mode 77 | if (program.debug) { 78 | command.push('--debug'); 79 | } 80 | 81 | command.push('index.js'); 82 | 83 | const monitor = respawn(command, { 84 | env, 85 | cwd: paths.dist, 86 | maxRestarts: 10, 87 | sleep: 300, 88 | stdio: 'inherit', 89 | }); 90 | 91 | runSequence([ 'clean', 'lint' ], 'transpile', () => { 92 | monitor.start(); 93 | done(); 94 | }); 95 | 96 | monitor 97 | .on('stdout', (data) => console.log(data.toString())) 98 | .on('stderr', (err) => console.error(err.toString())); 99 | 100 | function restartMonitor () { 101 | monitor.stop(() => monitor.start()); 102 | } 103 | 104 | gulp.watch(`${paths.src}/**/*.js`, (event) => { 105 | gutil.log(`File changed: ${gutil.colors.yellow(event.path)}`); 106 | 107 | let isLintError = false; 108 | 109 | lint(`${paths.src}/**/*.js`) 110 | .resume() 111 | .on('error', () => isLintError = true) 112 | .on('end', () => { 113 | if (isLintError) { 114 | return; 115 | } 116 | 117 | transpile(`${paths.src}/**/*.js`, paths.dist).on('end', restartMonitor); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "license": "ISC", 8 | "devDependencies": { 9 | "babel-eslint": "^5.0.0-beta4", 10 | "babel-plugin-transform-async-to-generator": "^6.1.18", 11 | "babel-plugin-transform-es2015-arrow-functions": "^6.1.18", 12 | "babel-plugin-transform-es2015-block-scoped-functions": "^6.1.18", 13 | "babel-plugin-transform-es2015-block-scoping": "^6.1.18", 14 | "babel-plugin-transform-es2015-classes": "^6.2.2", 15 | "babel-plugin-transform-es2015-computed-properties": "^6.1.18", 16 | "babel-plugin-transform-es2015-destructuring": "^6.1.18", 17 | "babel-plugin-transform-es2015-for-of": "^6.1.18", 18 | "babel-plugin-transform-es2015-function-name": "^6.1.18", 19 | "babel-plugin-transform-es2015-literals": "^6.1.18", 20 | "babel-plugin-transform-es2015-modules-commonjs": "^6.2.0", 21 | "babel-plugin-transform-es2015-object-super": "^6.1.18", 22 | "babel-plugin-transform-es2015-parameters": "^6.1.18", 23 | "babel-plugin-transform-es2015-shorthand-properties": "^6.1.18", 24 | "babel-plugin-transform-es2015-spread": "^6.1.18", 25 | "babel-plugin-transform-es2015-sticky-regex": "^6.1.18", 26 | "babel-plugin-transform-es2015-template-literals": "^6.1.18", 27 | "babel-plugin-transform-es2015-typeof-symbol": "^6.1.18", 28 | "babel-plugin-transform-es2015-unicode-regex": "^6.1.18", 29 | "babel-preset-stage-0": "^6.1.18", 30 | "commander": "^2.9.0", 31 | "del": "^2.1.0", 32 | "eslint-plugin-babel": "^3.0.0", 33 | "gulp": "^3.9.0", 34 | "gulp-babel": "^6.1.1", 35 | "gulp-cached": "^1.1.0", 36 | "gulp-eslint": "^1.1.1", 37 | "gulp-plumber": "^1.0.1", 38 | "gulp-sourcemaps": "^1.6.0", 39 | "gulp-util": "^3.0.7", 40 | "respawn": "^2.0.0", 41 | "run-sequence": "^1.1.5", 42 | "shelljs": "^0.5.3" 43 | }, 44 | "dependencies": { 45 | "core-js": "^1.2.6", 46 | "harmony-reflect": "^1.4.2", 47 | "koa": "^2.0.0-alpha.3", 48 | "koa-router": "^7.0.0", 49 | "source-map-support": "^0.4.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/controllers/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { readdirSync } from 'fs'; 3 | 4 | /** 5 | * Register all controllers with provided router 6 | * @param {KoaRouter} router API mount point 7 | */ 8 | export default function registerControllers (router) { 9 | readdirSync(__dirname) 10 | .filter((fileName) => fileName.endsWith('Ctrl.js')) 11 | .forEach((fileName) => { 12 | const ctrlFilePath = path.join(__dirname, fileName); 13 | 14 | require(ctrlFilePath).default(router); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/controllers/testCtrl.js: -------------------------------------------------------------------------------- 1 | import Model from 'models/Model'; 2 | 3 | const sayHi = async (ctx) => { 4 | ctx.body = 'Hello World'; 5 | }; 6 | 7 | async function loadData(ctx) { 8 | ctx.body = await Model.load(); 9 | } 10 | 11 | export default function(router) { 12 | router.get('/load-data', loadData); 13 | router.get('/say-hi', sayHi); 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js'; 2 | import 'harmony-reflect'; 3 | import { install } from 'source-map-support'; 4 | import Koa from 'koa'; 5 | import Router from 'koa-router'; 6 | import logger from 'lib/logger'; 7 | import registerControllers from 'controllers'; 8 | 9 | install(); 10 | 11 | const app = new Koa(); 12 | const router = new Router({ prefix: '/api' }); 13 | 14 | app.use(logger); 15 | app.use(router.routes()); 16 | 17 | registerControllers(router); 18 | 19 | if (!module.parent) { 20 | const port = process.env.PORT || 6060; 21 | 22 | app.listen(port, () => console.log(`✅ Listening on port ${port}...`)); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/logger.js: -------------------------------------------------------------------------------- 1 | export default async function (ctx, next) { 2 | const start = new Date(); 3 | 4 | await next(); 5 | 6 | const ms = new Date() - start; 7 | 8 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | export function sleep (ms) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /src/models/Model.js: -------------------------------------------------------------------------------- 1 | import { sleep } from 'lib/utils'; 2 | 3 | class Model { 4 | constructor(name, age) { 5 | this.name = name; 6 | this.age = age; 7 | } 8 | 9 | static async load() { 10 | await sleep(3000); 11 | 12 | return [ new this('Ondřej', 27), new this('Franta', 101) ]; 13 | } 14 | } 15 | 16 | export default Model; 17 | --------------------------------------------------------------------------------