├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── History.md ├── LICENSE ├── README.md ├── bin ├── debug.js ├── development.js └── production.js ├── nginx.conf ├── package.json ├── pm2.json ├── public ├── favicon.ico ├── robots.txt └── static │ └── stylesheets │ └── style.css ├── src ├── .gitkeep ├── app.js ├── config │ ├── default.js │ └── index.js ├── controllers │ ├── .gitkeep │ └── indexCtrl.js ├── index.js ├── models │ └── .gitkeep ├── routes │ └── index.js └── services │ └── .gitkeep ├── test └── test.js └── views ├── 404.ejs ├── 422.ejs ├── 500.ejs ├── error.ejs └── index.ejs /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-loose", "stage-3"], 3 | "plugins": ["add-module-exports", "transform-runtime"], 4 | "sourceMaps": true, 5 | "retainLines": true 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "standard", 4 | "env": { 5 | "node": true, 6 | "es6": true, 7 | "mocha": true 8 | }, 9 | "ecmaFeatures": { 10 | "arrowFunctions": true 11 | }, 12 | "rules": { 13 | "func-style": [2, "declaration", { "allowArrowFunctions": true }] 14 | } 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # app // compiled js 36 | app 37 | 38 | # VSCODE 39 | .vscode 40 | 41 | # JETBRAIN 42 | .idea 43 | 44 | src/config/* 45 | !src/config/index.js 46 | !src/config/default.js.idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - stable 5 | - 5 6 | - 4 7 | script: 8 | - npm test -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | #### 2016年06月28日 2 | 3 | - 重写 readme 4 | 5 | #### 2016年06月03日 6 | 7 | - 增加热替换 (HMR), 对 `controller` 的修改保存即生效, 不需要重启进程 8 | - `src` 目录下的删除, 新建文件等操作能够监听了, fix #4 9 | - 更新 README 10 | 11 | #### 2016年05月06日 12 | 13 | - 更新 README 14 | 15 | #### 2016年05月04日 16 | 17 | - 优化 development 模式下的热启动体验, 自动编译变化的单文件 18 | - 从 `babel-preset-es2015-node5` 切换到 `babel-preset-es2015` 19 | 20 | #### 2016年05月03日 21 | 22 | - 静态文件路径使用`/static/*`来做统一入口, 更新 nginx.conf 配置, 对静态资源线上环境使用 nginx代理 + etag/expires 指令. 23 | - 修改目录结构, test 目录挪到根目录下, 依然可以使用 ES6 书写测试用例. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 17koa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koa2-startkit 2 | [![Build Status](https://travis-ci.org/17koa/koa2-startkit.svg?branch=master)](https://travis-ci.org/17koa/koa2-startkit) 3 | 一个 Koa2 的脚手架 4 | 5 | 可以直接在项目里使用 ES6/7(Generator Function, Class, Async & Await)等特性,借助 Babel 编译,可稳定运行在 Node.js 环境上。 6 | 7 | [开发模式] 开发模式下,文件修改后~~自动重启 Node.js~~ 自动热重启服务。 8 | 9 | [调试模式] 断点调试 (test feature) 10 | 11 | [线上模式] 借助 pm2 使用 cluster 模式压榨多核 CPU 性能 12 | 13 | ## Getting Start 14 | 15 | ``` 16 | git clone https://github.com/17koa/koa2-startkit.git 17 | cd koa2-startkit 18 | npm install # 国内可以使用 cnpm 加速, 教育网可使用 rednpm (https://npm.mirror.cqupt.edu.cn) 加速 19 | npm start 20 | ``` 21 | 22 | 然后使用浏览器打开 http://127.0.0.1:3000/ 即可 23 | 24 | ## Npm scripts 25 | 26 | ```bash 27 | $ npm start # 开发模式, 开启开发模式之后对于 /src 目录内的任何改动会自动热替换生效 28 | $ npm run build # build 29 | $ npm test # 单元测试 30 | $ npm run compile # 编译 31 | $ npm run production # 生产模式 32 | ``` 33 | 34 | 35 | 36 | ## 线上部署 37 | 38 | ```bash 39 | npm run build # 单测, 编译 ES6/7 代码至 ES5 40 | vim pm2.json # 检查 pm2 的配置 41 | pm2 start pm2.json # 请确保已经 global 安装 pm2 (npm i pm2-cli -g) 42 | cp nginx.conf /etc/nginx/conf.d/YourProject.conf # 自行配置 nginx 反代 43 | ``` 44 | 45 | 46 | 47 | ## 配置文件的 trick 48 | 49 | 引用配置的方式: 50 | 51 | ```javascript 52 | import config from './config' 53 | ``` 54 | 55 | 默认配置文件位于 `src/config/default.js`, 建议只在这里创建配置字段, 在同目录下创建另一个 `custom.js`, 这个配置会覆盖(override) 默认配置, 且此文件不会被包含在 git 中, 避免密码泄露, 线上配置等问题. 56 | 57 | 58 | 59 | ## 断点调试 60 | 61 | [测试功能] 62 | 63 | ```bash 64 | $ npm run debug 65 | ``` 66 | 67 | 在 VSCode 编辑器中: 68 | 69 | ![1](https://dn-redrock.qbox.me/github/koa-1.png) 70 | 71 | 1. 选择DEBUG图标 72 | 2. 点击绿色三角, 环境选择 Node.js 73 | 3. 把program改成 ${workspaceRoot}/bin/debug.js, 把sourceMaps设为true 74 | 4. ![2](https://dn-redrock.qbox.me/github/koa-2.png) 75 | 5. 再次点击绿色三角启动debug 76 | 6. 进入 app/ 目录下找到对应的文件(!!注意是app目录), 在需要的地方打上断点(这里的代码是babel编译后的, 很难看懂啊, 但是在 node 支持 async, import 之前, 只能采用这种方法) 77 | 7. ![3](https://dn-redrock.qbox.me/github/koa-3.png) 78 | 8. 访问对应页面, vscode应该会弹出到断点处, 这个时候应该显示的就是 ES6/7 代码了 79 | 9. ![4](https://dn-redrock.qbox.me/github/koa-4.png) 80 | 10. 左侧的调试窗口已经可以正常使用了 81 | 11. ![5](https://dn-redrock.qbox.me/github/koa-5.png) 82 | 83 | 84 | 85 | 86 | ## 目录结构说明 87 | 88 | ```bash 89 | . 90 | ├── LICENSE 91 | ├── README.md 92 | ├── app # babel outDir 93 | │   ├── * 94 | ├── bin 95 | │   ├── debug.js 96 | │   ├── development.js # 开发模式下项目的入口文件 97 | │   └── production.js # 线上入口文件, 请预先使用 npm run compile 编译 98 | ├── nginx.conf # nginx 的配置文件,建议线上使用 nginx 做反向代理。 99 | ├── package.json # package.json 100 | ├── pm2.json # 用于 pm2 部署 101 | ├── public # 静态资源路径 102 | │   ├── favicon.ico 103 | │   ├── robots.txt 104 | │   └── static 105 | ├── src # 源代码目录,编译时会自动将 src 目录下的文件编译到 app 目录下。src 下的目录结构可以自行组织, 但是必须是 babel 可接受的类型(js, json, etc...)。 106 | │   ├── app.js # koa 配置 107 | │   ├── config # 配置目录 108 | │   ├── controllers # 控制器 109 | │   ├── index.js # 入口文件 110 | │   ├── models # 模型 111 | │   ├── routes # 路由 112 | │   └── services # service 113 | ├── test # 测试目录现在在项目根目录下 114 | │   └── test.js 115 | └── views # 视图(前端模板) 116 | ├── error.ejs 117 | └── index.ejs 118 | ``` 119 | 120 | 121 | ## Contact 122 | 123 | [issues](https://github.com/17koa/koa2-startkit/issues) 124 | 125 | [@Ling](https://github.com/wssgcg1213) -------------------------------------------------------------------------------- /bin/debug.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // debug 还是实验性功能 3 | var path = require('path') 4 | var babelCliDir = require('babel-cli/lib/babel/dir') 5 | require('colors') 6 | console.log('>>> [DEBUG]: Debug Mode is an expiremental feature'.cyan) 7 | console.log('>>> [DEBUG]: Compiling...'.green) 8 | babelCliDir({ outDir: 'app/', retainLines: true, sourceMaps: true }, [ 'src/' ]) // compile all when start 9 | 10 | try { 11 | require(path.join(__dirname, '../app')) 12 | } catch (e) { 13 | if (e && e.code === 'MODULE_NOT_FOUND') { 14 | console.log('>>> [DEBUG]: run `npm compile` first!') 15 | process.exit(1) 16 | } 17 | console.log('>>> [DEBUG]: App started with error and exited'.red, e) 18 | process.exit(1) 19 | } 20 | 21 | console.log('>>> [DEBUG]: App started in debug mode'.green) 22 | -------------------------------------------------------------------------------- /bin/development.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require('path') 3 | var projectRootPath = path.resolve(__dirname, '..') 4 | var srcPath = path.join(projectRootPath, 'src') 5 | var appPath = path.join(projectRootPath, 'app') 6 | var fs = require('fs') 7 | var debug = require('debug')('dev') 8 | require('colors') 9 | var log = console.log.bind(console, '>>> [DEV]:'.red) 10 | var babelCliDir = require('babel-cli/lib/babel/dir') 11 | var babelCliFile = require('babel-cli/lib/babel/file') 12 | var chokidar = require('chokidar') 13 | var watcher = chokidar.watch(path.join(__dirname, '../src')) 14 | 15 | watcher.on('ready', function () { 16 | log('Compiling...'.green) 17 | babelCliDir({ outDir: 'app/', retainLines: true, sourceMaps: true }, [ 'src/' ]) // compile all when start 18 | require('../app') // start app 19 | log('♪ App Started'.green) 20 | 21 | watcher 22 | .on('add', function (absPath) { 23 | compileFile('src/', 'app/', path.relative(srcPath, absPath), cacheClean) 24 | }) 25 | .on('change', function (absPath) { 26 | compileFile('src/', 'app/', path.relative(srcPath, absPath), cacheClean) 27 | }) 28 | .on('unlink', function (absPath) { 29 | var rmfileRelative = path.relative(srcPath, absPath) 30 | var rmfile = path.join(appPath, rmfileRelative) 31 | try { 32 | fs.unlinkSync(rmfile) 33 | fs.unlinkSync(rmfile + '.map') 34 | } catch (e) { 35 | debug('fail to unlink', rmfile) 36 | return 37 | } 38 | console.log('Deleted', rmfileRelative) 39 | cacheClean() 40 | }) 41 | }) 42 | 43 | 44 | function compileFile (srcDir, outDir, filename, cb) { 45 | var outFile = path.join(outDir, filename) 46 | var srcFile = path.join(srcDir, filename) 47 | try { 48 | babelCliFile({ 49 | outFile: outFile, 50 | retainLines: true, 51 | highlightCode: true, 52 | comments: true, 53 | babelrc: true, 54 | sourceMaps: true 55 | }, [ srcFile ], { highlightCode: true, comments: true, babelrc: true, ignore: [], sourceMaps: true }) 56 | } catch (e) { 57 | console.error('Error while compiling file %s', filename, e) 58 | return 59 | } 60 | console.log(srcFile + ' -> ' + outFile) 61 | cb && cb() 62 | } 63 | 64 | function cacheClean () { 65 | Object.keys(require.cache).forEach(function (id) { 66 | if (/[\/\\](app)[\/\\]/.test(id)) { 67 | delete require.cache[id] 68 | } 69 | }) 70 | log('♬ App Cache Cleaned...'.green) 71 | } 72 | 73 | process.on('exit', function (e) { 74 | log(' ♫ App Quit'.green) 75 | }) 76 | -------------------------------------------------------------------------------- /bin/production.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require('path') 3 | 4 | try { 5 | require(path.join(__dirname, '../app')) 6 | } catch (e) { 7 | if (e && e.code === 'MODULE_NOT_FOUND') { 8 | console.log('run `npm compile` first!') 9 | process.exit(1) 10 | } 11 | console.log('app started with error and exited', e) 12 | process.exit(1) 13 | } 14 | 15 | console.log('app started in production mode') 16 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | #root /path/to/your/project; 5 | set $node_port 3000; 6 | 7 | index index.html index.htm; 8 | 9 | location ~ /static/ { 10 | #root /path/to/your/project/public; # static directory here 11 | etag on; 12 | expires max; 13 | } 14 | 15 | location / { 16 | proxy_http_version 1.1; 17 | proxy_set_header Connection ""; 18 | proxy_set_header X-Real-IP $remote_addr; 19 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 20 | proxy_set_header Host $http_host; 21 | proxy_set_header X-NginX-Proxy true; 22 | proxy_set_header Upgrade $http_upgrade; 23 | proxy_set_header Connection "upgrade"; 24 | proxy_pass http://127.0.0.1:$node_port$request_uri; 25 | proxy_redirect off; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa2-startkit", 3 | "version": "0.2.0", 4 | "author": "wssgcg1213", 5 | "scripts": { 6 | "start": "node bin/development.js", 7 | "production": "node bin/production.js", 8 | "debug": "npm run clean && node bin/debug", 9 | "build": "npm test && npm run clean && npm run compile", 10 | "compile": "babel src/ --out-dir app/ --retain-lines --source-maps", 11 | "clean": "rm -rf app/", 12 | "test": "mocha -u bdd --compilers js:babel-core/register", 13 | "dev": "npm start" 14 | }, 15 | "engines": { 16 | "node": ">= 4" 17 | }, 18 | "dependencies": { 19 | "babel-cli": "^6.7.7", 20 | "babel-core": "^6.7.7", 21 | "babel-plugin-add-module-exports": "^0.1.4", 22 | "babel-plugin-transform-runtime": "^6.8.0", 23 | "babel-preset-es2015": "^6.6.0", 24 | "babel-preset-es2015-loose": "^7.0.0", 25 | "babel-preset-stage-3": "^6.5.0", 26 | "babel-runtime": "^6.6.1", 27 | "co": "^4.6.0", 28 | "debug": "^2.2.0", 29 | "ejs": "^2.4.1", 30 | "koa": "^2.0.0", 31 | "koa-bodyparser": "^2.0.1", 32 | "koa-convert": "^1.2.0", 33 | "koa-json": "^1.1.1", 34 | "koa-logger": "^1.3.0", 35 | "koa-onerror": "^3.0.1", 36 | "koa-router": "^7.0.0", 37 | "koa-static-plus": "^0.1.1", 38 | "koa-views": "^5.0.1", 39 | "lodash": "^4.11.1" 40 | }, 41 | "devDependencies": { 42 | "babel-eslint": "^6.0.4", 43 | "chokidar": "^1.5.1", 44 | "colors": "^1.1.2", 45 | "eslint": "^2.11.1", 46 | "eslint-config-standard": "^5.2.0", 47 | "eslint-plugin-promise": "^1.3.1", 48 | "eslint-plugin-standard": "^1.3.2", 49 | "mocha": "^2.4.5", 50 | "should": "^8.3.0", 51 | "supertest": "^1.2.0" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "git+https://github.com/17koa/koa2-startkit.git" 56 | }, 57 | "license": "MIT" 58 | } -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [{ 3 | "name": "koa2-startkit", 4 | "script": "bin/production.js", 5 | // "cwd": "/absolute/path/to/project", 6 | "exec_mode": "cluster", 7 | "instances": 0, 8 | "max_memory_restart": "1G", 9 | "autorestart": true, 10 | "node_args": [], 11 | "args": [], 12 | "env": { 13 | "NODE_ENV": "production" 14 | } 15 | }] 16 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/17koa/koa2-startkit/a14b95cc5ec7e942205c48ac5e5744e828a57fe0/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/static/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/17koa/koa2-startkit/a14b95cc5ec7e942205c48ac5e5744e828a57fe0/src/.gitkeep -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import http from 'http' 2 | import Koa from 'koa' 3 | import path from 'path' 4 | import views from 'koa-views' 5 | import convert from 'koa-convert' 6 | import json from 'koa-json' 7 | import Bodyparser from 'koa-bodyparser' 8 | import logger from 'koa-logger' 9 | import koaStatic from 'koa-static-plus' 10 | import koaOnError from 'koa-onerror' 11 | import config from './config' 12 | 13 | const app = new Koa() 14 | const bodyparser = Bodyparser() 15 | 16 | // middlewares 17 | app.use(convert(bodyparser)) 18 | app.use(convert(json())) 19 | app.use(convert(logger())) 20 | 21 | // static 22 | app.use(convert(koaStatic(path.join(__dirname, '../public'), { 23 | pathPrefix: '' 24 | }))) 25 | 26 | // views 27 | app.use(views(path.join(__dirname, '../views'), { 28 | extension: 'ejs' 29 | })) 30 | 31 | // 500 error 32 | koaOnError(app, { 33 | template: 'views/500.ejs' 34 | }) 35 | 36 | // logger 37 | app.use(async (ctx, next) => { 38 | const start = new Date() 39 | await next() 40 | const ms = new Date() - start 41 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) 42 | }) 43 | 44 | // response router 45 | app.use(async (ctx, next) => { 46 | await require('./routes').routes()(ctx, next) 47 | }) 48 | 49 | // 404 50 | app.use(async (ctx) => { 51 | ctx.status = 404 52 | await ctx.render('404') 53 | }) 54 | 55 | // error logger 56 | app.on('error', async (err, ctx) => { 57 | console.log('error occured:', err) 58 | }) 59 | 60 | const port = parseInt(config.port || '3000') 61 | const server = http.createServer(app.callback()) 62 | 63 | server.listen(port) 64 | server.on('error', (error) => { 65 | if (error.syscall !== 'listen') { 66 | throw error 67 | } 68 | // handle specific listen errors with friendly messages 69 | switch (error.code) { 70 | case 'EACCES': 71 | console.error(port + ' requires elevated privileges') 72 | process.exit(1) 73 | break 74 | case 'EADDRINUSE': 75 | console.error(port + ' is already in use') 76 | process.exit(1) 77 | break 78 | default: 79 | throw error 80 | } 81 | }) 82 | server.on('listening', () => { 83 | console.log('Listening on port: %d', port) 84 | }) 85 | 86 | export default app 87 | -------------------------------------------------------------------------------- /src/config/default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created at 16/4/11. 3 | * @Author Ling. 4 | * @Email i@zeroling.com 5 | */ 6 | export default { 7 | port: 3000, 8 | db: { 9 | host: 'localhost', 10 | database: 'koa2_startkit', 11 | username: 'root', 12 | password: '' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created at 16/4/11. 3 | * @Author Ling. 4 | * @Email i@zeroling.com 5 | */ 6 | import fs from 'fs' 7 | import lodash, { isPlainObject, defaultsDeep } from 'lodash' 8 | import defaultConfig from './default' 9 | 10 | const cfgs = [] 11 | fs.readdirSync(__dirname).map(filename => { 12 | if (filename === 'index.js') { 13 | return false 14 | } 15 | try { 16 | const cfg = require('./' + filename) 17 | if (isPlainObject(cfg)) { 18 | cfgs.push(cfg) 19 | } 20 | } catch (e) {} 21 | }) 22 | cfgs.push(defaultConfig) 23 | 24 | const config = defaultsDeep.apply(lodash, cfgs) 25 | export default config 26 | -------------------------------------------------------------------------------- /src/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/17koa/koa2-startkit/a14b95cc5ec7e942205c48ac5e5744e828a57fe0/src/controllers/.gitkeep -------------------------------------------------------------------------------- /src/controllers/indexCtrl.js: -------------------------------------------------------------------------------- 1 | export default async (ctx, next) => { 2 | const title = 'koa2 title' 3 | 4 | await ctx.render('index', { 5 | title 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app') -------------------------------------------------------------------------------- /src/models/.gitkeep: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import indexCtrl from '../controllers/indexCtrl' 3 | 4 | const router = Router() 5 | 6 | router.get('/', indexCtrl) 7 | 8 | export default router 9 | -------------------------------------------------------------------------------- /src/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/17koa/koa2-startkit/a14b95cc5ec7e942205c48ac5e5744e828a57fe0/src/services/.gitkeep -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import request from 'supertest' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import 'should' 5 | import app from '../src' 6 | 7 | describe('HTTP APP TEST', () => { 8 | describe('Koa GET /', () => { 9 | it('should 200', (done) => { 10 | request(app.listen()) 11 | .get('/') 12 | .set('Accept', 'application/text') 13 | .expect('Content-Type', /text/) 14 | .end((err, res) => { 15 | if (err) { 16 | throw new Error(err) 17 | } 18 | // console.log(res) 19 | res.status.should.equal(200) 20 | // console.log(res.text) 21 | res.text.should.equal("\n\n \n koa2 title\n \n \n \n

koa2 title

\n

EJS Welcome to koa2 title

\n \n\n") 22 | done() 23 | }) 24 | }) 25 | }) 26 | 27 | describe('Koa Static, GET /static/stylesheets/style.css', () => { 28 | it('should 200', (done) => { 29 | const styleCssContent = fs.readFileSync(path.join(__dirname, '../public/static/stylesheets/style.css'), 'utf-8') 30 | request(app.listen()) 31 | .get('/static/stylesheets/style.css') 32 | .set('Accept', 'application/text') 33 | .expect('Content-Type', /text/) 34 | .end((err, res) => { 35 | if (err) { 36 | throw new Error(err) 37 | } 38 | // console.log(res) 39 | res.status.should.equal(200) 40 | // console.log(res.text) 41 | res.text.should.equal(styleCssContent) 42 | done() 43 | }) 44 | }) 45 | }) 46 | 47 | describe('GET /pathNotMatchAny', () => { 48 | it('should 404', (done) => { 49 | request(app.listen()) 50 | .get('/pathNotMatchAny') 51 | .set('Accept', 'application/text') 52 | .expect('Content-Type', /text/) 53 | .end((err, res) => { 54 | if (err) { 55 | throw new Error(err) 56 | } 57 | // console.log(res) 58 | res.status.should.equal(404) 59 | done() 60 | }) 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /views/422.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /views/500.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

EJS Welcome to <%= title %>

10 | 11 | 12 | --------------------------------------------------------------------------------