├── 404
├── test.js
└── app.js
├── .jshintignore
├── templates
├── views
│ └── user.ejs
├── test.js
└── app.js
├── .eslintrc.yml
├── .travis.yml
├── blog
├── views
│ ├── index.html
│ ├── show.html
│ ├── new.html
│ ├── list.html
│ └── layout.html
├── lib
│ └── render.js
├── app.js
└── test.js
├── hello-world
├── app.js
└── test.js
├── stream-view
├── app.js
├── test.js
├── view.js
└── README.md
├── .editorconfig
├── upload
├── public
│ ├── 404.html
│ └── index.html
└── app.js
├── vhost
├── apps
│ ├── koa.js
│ └── array.js
├── app.js
└── test.js
├── stream-server-side-events
├── README.md
├── sse.js
├── db.js
└── app.js
├── cookies
├── app.js
└── test.js
├── body-parsing
├── app.js
└── test.js
├── stream-objects
├── test.js
├── app.js
└── README.md
├── base-auth
├── app.js
└── test.js
├── errors
├── test.js
└── app.js
├── stream-file
├── app.js
├── test.js
└── README.md
├── compose
├── test.js
└── app.js
├── csrf
├── app.js
└── test.js
├── conditional-middleware
└── app.js
├── .gitignore
├── flash-messages
├── test.js
└── app.js
├── package.json
├── multipart
├── app.js
└── test.js
├── negotiation
├── test.js
└── app.js
└── Readme.md
/.jshintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | */test.js
--------------------------------------------------------------------------------
/templates/views/user.ejs:
--------------------------------------------------------------------------------
1 |
<%= user.name.first %> is a <%= user.age %> year old <%= user.species %>.
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | env:
2 | mocha: true
3 | extends: koa
4 | rules:
5 | no-var: 0
6 | prefer-arrow-callback: 0
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | node_js:
2 | - stable
3 | - 8
4 | language: node_js
5 | script:
6 | - npm run lint
7 | - npm test
8 |
--------------------------------------------------------------------------------
/blog/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Blog
4 |
5 |
6 | {% for post in posts %}
7 | {% endfor %}
8 |
9 |
--------------------------------------------------------------------------------
/blog/views/show.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 |
3 | {% block title %}{{ post.title }}{% endblock %}
4 |
5 | {% block content %}
6 | {{ post.title }}
7 | {{ post.body }}
8 | {% endblock %}
--------------------------------------------------------------------------------
/hello-world/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const app = module.exports = new Koa();
3 |
4 | app.use(async function(ctx) {
5 | ctx.body = 'Hello World';
6 | });
7 |
8 | if (!module.parent) app.listen(3000);
9 |
--------------------------------------------------------------------------------
/stream-view/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 |
3 | const View = require('./view');
4 |
5 | const app = module.exports = new Koa();
6 |
7 | app.use(async function(ctx) {
8 | ctx.type = 'html';
9 | ctx.body = new View(ctx);
10 | });
11 |
12 | if (!module.parent) app.listen(3000);
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/blog/lib/render.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | const views = require('koa-views');
7 | const path = require('path');
8 |
9 | // setup views mapping .html
10 | // to the swig template engine
11 |
12 | module.exports = views(path.join(__dirname, '/../views'), {
13 | map: { html: 'swig' }
14 | });
15 |
--------------------------------------------------------------------------------
/upload/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Not Found
4 |
10 |
11 |
12 | Sorry! Can't find that.
13 | The page you requested cannot be found.
14 |
15 |
--------------------------------------------------------------------------------
/vhost/apps/koa.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 |
3 | // koa app
4 |
5 | const app = new Koa();
6 |
7 | app.use(async function(ctx, next) {
8 | await next();
9 | ctx.set('X-Custom', 'Dub Dub Dub App');
10 | });
11 |
12 | app.use(async function(ctx, next) {
13 | await next();
14 | if ('/' != ctx.url) return;
15 | ctx.body = 'Hello from www app';
16 | });
17 |
18 | module.exports = app;
19 |
--------------------------------------------------------------------------------
/hello-world/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Hello World', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | it('should say "Hello World"', function(done) {
11 | request
12 | .get('/')
13 | .expect(200)
14 | .expect('Hello World', done);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/stream-server-side-events/README.md:
--------------------------------------------------------------------------------
1 | # stream-server-side-events
2 |
3 | This program continually sends events to the web browser. It simulates a series of log file entries that have been appended to a file.
4 |
5 | To invoke, the following command begins listening on localhost:3000.
6 |
7 | node --harmony app.js
8 |
9 | To see results:
10 |
11 | http://localhost:3000
12 |
13 | ## Interesting points
14 |
15 | 1.
16 |
--------------------------------------------------------------------------------
/cookies/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This example simply sets the number of views from the same client
3 | * both as a cookie and as a response string.
4 | */
5 |
6 | const Koa = require('koa');
7 | const app = module.exports = new Koa();
8 |
9 | app.use(async function(ctx) {
10 | const n = ~~ctx.cookies.get('view') + 1;
11 | ctx.cookies.set('view', n);
12 | ctx.body = n + ' views';
13 | });
14 |
15 | if (!module.parent) app.listen(3000);
16 |
--------------------------------------------------------------------------------
/blog/views/new.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 |
3 | {% block title %}New Post{% endblock %}
4 |
5 | {% block content %}
6 | New Post
7 | Create a new post.
8 |
13 | {% endblock %}
--------------------------------------------------------------------------------
/404/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('404', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('when GET /', function() {
11 | it('should return the 404 page', function(done) {
12 | request
13 | .get('/')
14 | .expect(404)
15 | .expect(/Page Not Found/, done);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/blog/views/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 |
3 | {% block title %}Posts{% endblock %}
4 |
5 | {% block content %}
6 | Posts
7 | You have {{ posts.length }} posts!
8 | Create a Post
9 |
10 | {% for post in posts %}
11 | -
12 |
{{ post.title }}
13 | Read post
14 |
15 | {% endfor %}
16 |
17 | {% endblock %}
--------------------------------------------------------------------------------
/templates/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Templates', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('GET /', function() {
11 | it('should respond with a rendered view', function(done) {
12 | request
13 | .get('/')
14 | .expect(200)
15 | .expect('Tobi is a 3 year old ferret.
', done);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/upload/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Upload
5 |
11 |
12 |
13 | File Upload
14 | Try uploading multiple files at a time.
15 |
19 |
20 |
--------------------------------------------------------------------------------
/vhost/apps/array.js:
--------------------------------------------------------------------------------
1 | // rather than koa apps we can also use array
2 | // bundles of middleware to the same effect.
3 |
4 | async function responseTime(ctx, next) {
5 | const start = new Date();
6 | await next();
7 | const ms = new Date() - start;
8 | ctx.set('X-Response-Time', ms + 'ms');
9 | }
10 |
11 | async function index(ctx, next) {
12 | await next();
13 | if ('/' != ctx.url) return;
14 | ctx.body = 'Howzit? From bar middleware bundle';
15 | }
16 |
17 | module.exports = [
18 | responseTime,
19 | index
20 | ];
21 |
--------------------------------------------------------------------------------
/body-parsing/app.js:
--------------------------------------------------------------------------------
1 |
2 | const Koa = require('koa');
3 | const koaBody = require('koa-body');
4 |
5 | const app = module.exports = new Koa();
6 |
7 | app.use(koaBody({
8 | jsonLimit: '1kb'
9 | }));
10 |
11 | // POST .name to /uppercase
12 | // co-body accepts application/json
13 | // and application/x-www-form-urlencoded
14 |
15 | app.use(async function(ctx) {
16 | const body = ctx.request.body;
17 | if (!body.name) ctx.throw(400, '.name required');
18 | ctx.body = { name: body.name.toUpperCase() };
19 | });
20 |
21 | if (!module.parent) app.listen(3000);
22 |
--------------------------------------------------------------------------------
/stream-objects/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const app = require('./app');
3 | const server = app.listen();
4 | const request = require('supertest').agent(server);
5 |
6 | describe('Stream Objects', function() {
7 | after(function() {
8 | server.close();
9 | });
10 |
11 | it('GET /', function(done) {
12 | request
13 | .get('/app.js')
14 | .expect(200, function(err, res) {
15 | if (err) return done(err);
16 |
17 | res.body.should.eql([{
18 | id: 1
19 | }, {
20 | id: 2
21 | }]);
22 | done();
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/stream-view/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const app = require('./app');
3 | const server = app.listen();
4 | const request = require('supertest').agent(server);
5 |
6 | describe('Stream View', function() {
7 | after(function() {
8 | server.close();
9 | });
10 |
11 | it('GET /', function(done) {
12 | request
13 | .get('/')
14 | .expect(200, function(err, res) {
15 | if (err) return done(err);
16 |
17 | res.should.be.html;
18 | res.text.should.include('Hello World');
19 | res.text.should.include('Hello World
');
20 | done();
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/cookies/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Cookies Views', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | [1, 2, 3].forEach(function(i) {
11 | describe('on iteration #' + i, function() {
12 | it('should set the views as a cookie and as the body', function(done) {
13 | request
14 | .get('/')
15 | .expect(200)
16 | .expect('Set-Cookie', new RegExp('view=' + i))
17 | .expect(i + ' views', done);
18 | });
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/stream-server-side-events/sse.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create a transform stream that converts a stream
3 | * to valid `data: \n\n' events for SSE.
4 | */
5 |
6 | var Transform = require('stream').Transform;
7 | var inherits = require('util').inherits;
8 |
9 | module.exports = SSE;
10 |
11 | inherits(SSE, Transform);
12 |
13 | function SSE(options) {
14 | if (!(this instanceof SSE)) return new SSE(options);
15 |
16 | options = options || {};
17 | Transform.call(this, options);
18 | }
19 |
20 | SSE.prototype._transform = function(data, enc, cb) {
21 | this.push('data: ' + data.toString('utf8') + '\n\n');
22 | cb();
23 | };
24 |
--------------------------------------------------------------------------------
/templates/app.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const views = require('koa-views');
3 | const Koa = require('koa');
4 | const app = module.exports = new Koa();
5 |
6 | // setup views, appending .ejs
7 | // when no extname is given to render()
8 |
9 | app.use(views(path.join(__dirname, '/views'), { extension: 'ejs' }));
10 |
11 | // dummy data
12 |
13 | const user = {
14 | name: {
15 | first: 'Tobi',
16 | last: 'Holowaychuk'
17 | },
18 | species: 'ferret',
19 | age: 3
20 | };
21 |
22 | // render
23 |
24 | app.use(async function(ctx) {
25 | await ctx.render('user', { user });
26 | });
27 |
28 | if (!module.parent) app.listen(3000);
29 |
--------------------------------------------------------------------------------
/stream-objects/app.js:
--------------------------------------------------------------------------------
1 |
2 | const Koa = require('koa');
3 | const JSONStream = require('streaming-json-stringify');
4 |
5 | const app = module.exports = new Koa();
6 |
7 | app.use(async function(ctx) {
8 | ctx.type = 'json';
9 | const stream = ctx.body = JSONStream();
10 |
11 | stream.on('error', ctx.onerror);
12 |
13 | setImmediate(function() {
14 | stream.write({
15 | id: 1
16 | });
17 |
18 | setImmediate(function() {
19 | stream.write({
20 | id: 2
21 | });
22 |
23 | setImmediate(function() {
24 | stream.end();
25 | });
26 | });
27 | });
28 | });
29 |
30 | if (!module.parent) app.listen(3000);
31 |
--------------------------------------------------------------------------------
/404/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 |
3 | const app = module.exports = new Koa();
4 |
5 | app.use(async function pageNotFound(ctx) {
6 | // we need to explicitly set 404 here
7 | // so that koa doesn't assign 200 on body=
8 | ctx.status = 404;
9 |
10 | switch (ctx.accepts('html', 'json')) {
11 | case 'html':
12 | ctx.type = 'html';
13 | ctx.body = 'Page Not Found
';
14 | break;
15 | case 'json':
16 | ctx.body = {
17 | message: 'Page Not Found'
18 | };
19 | break;
20 | default:
21 | ctx.type = 'text';
22 | ctx.body = 'Page Not Found';
23 | }
24 | });
25 |
26 | if (!module.parent) app.listen(3000);
27 |
--------------------------------------------------------------------------------
/base-auth/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const auth = require('koa-basic-auth');
3 |
4 | const app = module.exports = new Koa();
5 |
6 | // custom 401 handling
7 |
8 | app.use(async function(ctx, next) {
9 | try {
10 | await next();
11 | } catch (err) {
12 | if (err.status === 401) {
13 | ctx.status = 401;
14 | ctx.set('WWW-Authenticate', 'Basic');
15 | ctx.body = 'cant haz that';
16 | } else {
17 | throw err;
18 | }
19 | }
20 | });
21 |
22 | // require auth
23 |
24 | app.use(auth({ name: 'tj', pass: 'tobi' }));
25 |
26 | // secret response
27 |
28 | app.use(async function(ctx) {
29 | ctx.body = 'secret';
30 | });
31 |
32 | if (!module.parent) app.listen(3000);
33 |
--------------------------------------------------------------------------------
/errors/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const app = require('./app');
3 | const server = app.listen();
4 | const request = require('supertest').agent(server);
5 |
6 | describe('Errors', function() {
7 | after(function() {
8 | server.close();
9 | });
10 |
11 | it('should catch the error', function(done) {
12 | request
13 | .get('/')
14 | .expect(500)
15 | .expect('Content-Type', /text\/html/, done);
16 | });
17 |
18 | it('should emit the error on app', function(done) {
19 | app.once('error', function(err, ctx) {
20 | err.message.should.equal('boom boom');
21 | ctx.should.be.ok;
22 | done();
23 | });
24 |
25 | request
26 | .get('/')
27 | .end(function() {});
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/stream-server-side-events/db.js:
--------------------------------------------------------------------------------
1 | var Readable = require('stream').Readable;
2 | var inherits = require('util').inherits;
3 |
4 | /**
5 | * Returns a new subscription event event.
6 | * Real APIs would care about the `event`.
7 | */
8 |
9 | exports.subscribe = function(event, options) {
10 | return Subscription(options);
11 | };
12 |
13 | /**
14 | * Subscription stream. Just increments the result.
15 | * Never ends!
16 | */
17 |
18 | inherits(Subscription, Readable);
19 |
20 | function Subscription(options) {
21 | if (!(this instanceof Subscription)) return new Subscription(options);
22 |
23 | options = options || {};
24 | Readable.call(this, options);
25 |
26 | this.value = 0;
27 | }
28 |
29 | Subscription.prototype._read = function() {
30 | while (this.push(String(this.value++))) {}
31 | };
32 |
--------------------------------------------------------------------------------
/stream-file/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const fs = require('fs');
3 | const app = module.exports = new Koa();
4 | const path = require('path');
5 | const extname = path.extname;
6 |
7 | // try GET /app.js
8 |
9 | app.use(async function(ctx) {
10 | const fpath = path.join(__dirname, ctx.path);
11 | const fstat = await stat(fpath);
12 |
13 | if (fstat.isFile()) {
14 | ctx.type = extname(fpath);
15 | ctx.body = fs.createReadStream(fpath);
16 | }
17 | });
18 |
19 | if (!module.parent) app.listen(3000);
20 |
21 | /**
22 | * thunkify stat
23 | */
24 |
25 | function stat(file) {
26 | return new Promise(function(resolve, reject) {
27 | fs.stat(file, function(err, stat) {
28 | if (err) {
29 | reject(err);
30 | } else {
31 | resolve(stat);
32 | }
33 | });
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/compose/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Compose', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('when GET /', function() {
11 | it('should say "Hello World"', function(done) {
12 | request
13 | .get('/')
14 | .expect(200)
15 | .expect('Hello World', done);
16 | });
17 |
18 | it('should set X-Response-Time', function(done) {
19 | request
20 | .get('/')
21 | .expect('X-Response-Time', /ms$/)
22 | .expect(200, done);
23 | });
24 | });
25 |
26 | describe('when not GET /', function() {
27 | it('should 404', function(done) {
28 | request
29 | .get('/aklsjdf')
30 | .expect(404, done);
31 | });
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/csrf/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const koaBody = require('koa-body');
3 | const session = require('koa-session');
4 | const CSRF = require('koa-csrf');
5 | const router = require('@koa/router')();
6 |
7 | const app = module.exports = new Koa();
8 |
9 | /**
10 | * csrf need session
11 | */
12 |
13 | app.keys = ['session key', 'csrf example'];
14 | app.use(session(app));
15 | app.use(koaBody());
16 |
17 | /**
18 | * maybe a bodyparser
19 | */
20 |
21 | /**
22 | * csrf middleware
23 | */
24 |
25 | app.use(new CSRF());
26 |
27 | /**
28 | * route
29 | */
30 |
31 | router.get('/token', token)
32 | .post('/post', post);
33 |
34 | app.use(router.routes());
35 |
36 | async function token(ctx) {
37 | ctx.body = ctx.csrf;
38 | }
39 |
40 | async function post(ctx) {
41 | ctx.body = {ok: true};
42 | }
43 |
44 | if (!module.parent) app.listen(3000);
45 |
--------------------------------------------------------------------------------
/stream-file/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Stream File', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | it('GET /app.js', function(done) {
11 | request
12 | .get('/app.js')
13 | .expect('content-type', /application\/javascript/)
14 | .expect(200, done);
15 | });
16 |
17 | it('GET /test.js', function(done) {
18 | request
19 | .get('/test.js')
20 | .expect('content-type', /application\/javascript/)
21 | .expect(200, done);
22 | });
23 |
24 | it('GET /alksjdf.js', function(done) {
25 | request
26 | .get('/lajksdf.js')
27 | .expect(404, done);
28 | });
29 |
30 | it('GET /', function(done) {
31 | request
32 | .get('/')
33 | .expect(404, done);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/stream-view/view.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Readable = require('stream').Readable;
4 | const co = require('co');
5 |
6 | module.exports = class View extends Readable {
7 |
8 | constructor(context) {
9 | super();
10 |
11 | // render the view on a different loop
12 | co.call(this, this.render).catch(context.onerror);
13 | }
14 |
15 | _read() {}
16 |
17 | *render() {
18 | // push the immediately
19 | this.push('Hello World');
20 |
21 | // render the on the next tick
22 | const body = yield done => {
23 | setImmediate(() => done(null, 'Hello World
'));
24 | };
25 |
26 | this.push('' + body + '');
27 |
28 | // close the document
29 | this.push('');
30 |
31 | // end the stream
32 | this.push(null);
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/conditional-middleware/app.js:
--------------------------------------------------------------------------------
1 | const logger = require('koa-logger');
2 | const Koa = require('koa');
3 | const app = new Koa();
4 |
5 | // passing any middleware to this middleware
6 | // will make it conditional, and will not be used
7 | // when an asset is requested, illustrating how
8 | // middleware may "wrap" other middleware.
9 |
10 | function ignoreAssets(mw) {
11 | return async function(ctx, next) {
12 | if (/(\.js|\.css|\.ico)$/.test(ctx.path)) {
13 | await next();
14 | } else {
15 | // must .call() to explicitly set the receiver
16 | await mw.call(this, ctx, next);
17 | }
18 | };
19 | }
20 |
21 | // TRY:
22 | // $ curl http://localhost:3000/
23 | // $ curl http://localhost:3000/style.css
24 | // $ curl http://localhost:3000/some.html
25 |
26 | app.use(ignoreAssets(logger()));
27 |
28 | app.use(async function(ctx) {
29 | ctx.body = 'Hello World';
30 | });
31 |
32 | app.listen(3000);
33 |
--------------------------------------------------------------------------------
/base-auth/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Koa Basic Auth', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('with no credentials', function() {
11 | it('should `throw` 401', function(done) {
12 | request
13 | .get('/')
14 | .expect(401, done);
15 | });
16 | });
17 |
18 | describe('with invalid credentials', function() {
19 | it('should `throw` 401', function(done) {
20 | request
21 | .get('/')
22 | .auth('user', 'invalid password')
23 | .expect(401, done);
24 | });
25 | });
26 |
27 | describe('with valid credentials', function() {
28 | it('should call the next middleware', function(done) {
29 | request
30 | .get('/')
31 | .auth('tj', 'tobi')
32 | .expect(200)
33 | .expect('secret', done);
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/stream-server-side-events/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const app = module.exports = new Koa();
3 |
4 | const sse = require('./sse');
5 | const db = require('./db');
6 |
7 | app.use(async function(ctx) {
8 | // otherwise node will automatically close this connection in 2 minutes
9 | ctx.req.setTimeout(Number.MAX_VALUE);
10 |
11 | ctx.type = 'text/event-stream; charset=utf-8';
12 | ctx.set('Cache-Control', 'no-cache');
13 | ctx.set('Connection', 'keep-alive');
14 |
15 | const body = ctx.body = sse();
16 | const stream = db.subscribe('some event');
17 | stream.pipe(body);
18 |
19 | // if the connection closes or errors,
20 | // we stop the SSE.
21 | const socket = ctx.socket;
22 | socket.on('error', close);
23 | socket.on('close', close);
24 |
25 | function close() {
26 | stream.unpipe(body);
27 | socket.removeListener('error', close);
28 | socket.removeListener('close', close);
29 | }
30 | });
31 |
32 | if (!module.parent) app.listen(3000);
33 |
--------------------------------------------------------------------------------
/stream-view/README.md:
--------------------------------------------------------------------------------
1 | # stream-view
2 |
3 | This is a "Hello World" application, using a view that inherits from a Readable stream.
4 |
5 | To invoke, the following command begins listening on localhost:3000.
6 |
7 | node app.js
8 |
9 | To see results:
10 |
11 | http://localhost:3000
12 |
13 | ## Interesting points
14 |
15 | 1. The main function of app.js instantiates a "View" from the view.js file.
16 | 2. The View overrides the Readable's _read() function with an empty function.
17 | 3. The View also overrides the Readable's render() function to do the following:
18 | 1. Immediately push out the text for the \ of the page
19 | 2. Yield to a function that will ultimately (in the next tick) return the "Hello World" text. The render() function pauses at that point.
20 | 3. When that function returns, render() resumes, and assigns the return value to the `body` variable
21 | 4. Push out the returned text wrapped in \ tags
22 | 5. Push out the closing \ tag and
23 | 6. Close the connection with push(null)
24 |
--------------------------------------------------------------------------------
/errors/app.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const app = module.exports = new Koa();
3 |
4 | // look ma, error propagation!
5 |
6 | app.use(async function(ctx, next) {
7 | try {
8 | await next();
9 | } catch (err) {
10 | // some errors will have .status
11 | // however this is not a guarantee
12 | ctx.status = err.status || 500;
13 | ctx.type = 'html';
14 | ctx.body = 'Something exploded, please contact Maru.
';
15 |
16 | // since we handled this manually we'll
17 | // want to delegate to the regular app
18 | // level error handling as well so that
19 | // centralized still functions correctly.
20 | ctx.app.emit('error', err, ctx);
21 | }
22 | });
23 |
24 | // response
25 |
26 | app.use(async function() {
27 | throw new Error('boom boom');
28 | });
29 |
30 | // error handler
31 |
32 | app.on('error', function(err) {
33 | if (process.env.NODE_ENV != 'test') {
34 | console.log('sent error %s to the cloud', err.message);
35 | console.log(err);
36 | }
37 | });
38 |
39 | if (!module.parent) app.listen(3000);
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store*
32 | ehthumbs.db
33 | Icon?
34 | Thumbs.db
35 | .idea
36 |
37 | # Node.js #
38 | ###########
39 | lib-cov
40 | *.seed
41 | *.log
42 | *.csv
43 | *.dat
44 | *.out
45 | *.pid
46 | *.gz
47 |
48 | pids
49 | logs
50 | results
51 |
52 | node_modules
53 | npm-debug.log
54 |
55 | # Components #
56 | ##############
57 |
58 | /build
59 | /components
60 |
61 | # ImageMagick #
62 | ###############
63 |
64 | *.cache
65 | *.mpc
66 |
67 | # Other #
68 | #########
69 | test/*.2
70 | test/*/*.2
71 | test/*.mp4
72 | test/images/originalSideways.jpg.2
73 |
74 | # Traceur output #
75 | ##################
76 | out/*
--------------------------------------------------------------------------------
/stream-objects/README.md:
--------------------------------------------------------------------------------
1 | # stream-objects
2 |
3 | Stream a out Javascript objects. To invoke, the following command begins listening on localhost:3000.
4 |
5 | node --harmony app.js
6 |
7 | To see results:
8 |
9 | http://localhost:3000
10 |
11 | ## Interesting points
12 |
13 | 1. In app.js, the setImmediate() function writes out a JS object { id: 1 } to the stream, then declares another setImmediate function.
14 | 2. The second setImmediate() function writes a second JS object { id: 2 } to the stream, then declares a third setImmediate() function.
15 | 3. The final setImmediate() calls stream.end() to indicate that there is no more data.
16 | 4. Note that the setImmediate() calls do **not** happen at the same moment. The first setImmediate() call is executed sometime after the initial request arrived. That setImmediate() call then declares the second setImmediate() call, which happens at least one tick later, and the third setImmediate() call happens in a separate tick.
17 | 4. The resulting web page shows an array containing both the JS objects, in the order they were initiated, like this:
18 |
19 | [
20 | {"id":1}
21 | ,
22 | {"id":2}
23 | ]
24 |
--------------------------------------------------------------------------------
/flash-messages/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const app = require('./app');
3 | const server = app.listen();
4 | const request = require('supertest').agent(server);
5 |
6 | describe('Flash Messages', function() {
7 | after(function() {
8 | server.close();
9 | });
10 |
11 | it('GET should return an empty array', function(done) {
12 | request
13 | .get('/messages')
14 | .expect(200)
15 | .expect('content-type', 'application/json; charset=utf-8')
16 | .expect('[]', done);
17 | });
18 |
19 | it('POST should return 204', function(done) {
20 | request
21 | .post('/messages')
22 | .send('hello')
23 | .expect(204, done);
24 | });
25 |
26 | it('GET should return the message', function(done) {
27 | request
28 | .get('/messages')
29 | .expect(200)
30 | .expect('content-type', 'application/json; charset=utf-8')
31 | .expect('["hello"]', done);
32 | });
33 |
34 | it('GET should return no more messages', function(done) {
35 | request
36 | .get('/messages')
37 | .expect(200)
38 | .expect('content-type', 'application/json; charset=utf-8')
39 | .expect('[]', done);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/blog/views/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% block title %}Blog{% endblock %}
4 |
54 |
55 |
56 |
57 | {% block content %}
58 | Missing content!
59 | {% endblock %}
60 |
61 |
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "koa-examples",
3 | "description": "Examples using Koa",
4 | "version": "0.0.1",
5 | "repository": "https://github.com/koajs/examples",
6 | "bugs": "https://github.com/koajs/examples/issues",
7 | "dependencies": {
8 | "ejs": "^2.5.6",
9 | "fs-promise": "^2.0.3",
10 | "koa": "^2.2.0",
11 | "koa-basic-auth": "^2.0.0",
12 | "koa-body": "^4.0.8",
13 | "koa-compose": "^4.0.0",
14 | "koa-csrf": "^3.0.6",
15 | "koa-logger": "^3.0.0",
16 | "@koa/router": "^8.0.5",
17 | "koa-session": "^5.0.0",
18 | "koa-static": "^3.0.0",
19 | "koa-views": "^6.0.2",
20 | "streaming-json-stringify": "^3.1.0",
21 | "swig-templates": "^2.0.3"
22 | },
23 | "devDependencies": {
24 | "eslint": "^3.8.1",
25 | "eslint-config-koa": "^2.0.2",
26 | "eslint-config-standard": "^6.2.0",
27 | "eslint-plugin-promise": "^3.3.0",
28 | "eslint-plugin-standard": "^2.0.1",
29 | "mocha": "^5.0.0",
30 | "should": "^3.3.2",
31 | "supertest": "^3.0.0"
32 | },
33 | "scripts": {
34 | "test": "NODE_ENV=test mocha --harmony --reporter spec --require should */test.js",
35 | "lint": "eslint ."
36 | },
37 | "engines": {
38 | "node": ">= 7.6"
39 | },
40 | "license": "MIT"
41 | }
42 |
--------------------------------------------------------------------------------
/flash-messages/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A very simple flash example.
3 | * Only uses JSON for simplicity.
4 | */
5 |
6 | const Koa = require('koa');
7 | const rawBody = require('raw-body');
8 | const session = require('koa-session');
9 |
10 | const app = module.exports = new Koa();
11 |
12 | // required for signed cookie sessions
13 | app.keys = ['key1', 'key2'];
14 | app.use(session(app));
15 |
16 | app.use(async function(ctx, next) {
17 | if (ctx.method !== 'GET' || ctx.path !== '/messages') return await next();
18 |
19 | // get any messages saved in the session
20 | const messages = ctx.session.messages || [];
21 | ctx.body = messages;
22 |
23 | // delete the messages as they've been deliverd
24 | delete ctx.session.messages;
25 | });
26 |
27 | app.use(async function(ctx, next) {
28 | if (ctx.method !== 'POST' || ctx.path !== '/messages') return await next();
29 |
30 | // the request string is the flash message
31 | const message = await rawBody(ctx.req, {
32 | encoding: 'utf8'
33 | });
34 |
35 | // push the message to the session
36 | ctx.session.messages = ctx.session.messages || [];
37 | ctx.session.messages.push(message);
38 |
39 | // tell the client everything went okay
40 | ctx.status = 204;
41 | });
42 |
43 | if (!module.parent) app.listen(3000);
44 |
--------------------------------------------------------------------------------
/upload/app.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | const logger = require('koa-logger');
7 | const serve = require('koa-static');
8 | const koaBody = require('koa-body');
9 | const Koa = require('koa');
10 | const fs = require('fs');
11 | const app = new Koa();
12 | const os = require('os');
13 | const path = require('path');
14 |
15 | // log requests
16 |
17 | app.use(logger());
18 |
19 | app.use(koaBody({ multipart: true }));
20 |
21 | // custom 404
22 |
23 | app.use(async function(ctx, next) {
24 | await next();
25 | if (ctx.body || !ctx.idempotent) return;
26 | ctx.redirect('/404.html');
27 | });
28 |
29 | // serve files from ./public
30 |
31 | app.use(serve(path.join(__dirname, '/public')));
32 |
33 | // handle uploads
34 |
35 | app.use(async function(ctx, next) {
36 | // ignore non-POSTs
37 | if ('POST' != ctx.method) return await next();
38 |
39 | const file = ctx.request.files.file;
40 | const reader = fs.createReadStream(file.path);
41 | const stream = fs.createWriteStream(path.join(os.tmpdir(), Math.random().toString()));
42 | reader.pipe(stream);
43 | console.log('uploading %s -> %s', file.name, stream.path);
44 |
45 | ctx.redirect('/');
46 | });
47 |
48 | // listen
49 |
50 | app.listen(3000);
51 | console.log('listening on port 3000');
52 |
--------------------------------------------------------------------------------
/multipart/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Multipart example downloading all the files to disk using co-busboy.
3 | * If all you want is to download the files to a temporary folder,
4 | * just use https://github.com/cojs/multipart instead of copying this code
5 | * as it handles file descriptor limits whereas this does not.
6 | */
7 |
8 | const os = require('os');
9 | const path = require('path');
10 | const Koa = require('koa');
11 | const fs = require('fs-promise');
12 | const koaBody = require('koa-body');
13 |
14 | const app = module.exports = new Koa();
15 |
16 | app.use(koaBody({ multipart: true }));
17 |
18 | app.use(async function(ctx) {
19 | // create a temporary folder to store files
20 | const tmpdir = path.join(os.tmpdir(), uid());
21 |
22 | // make the temporary directory
23 | await fs.mkdir(tmpdir);
24 | const filePaths = [];
25 | const files = ctx.request.files || {};
26 |
27 | for (let key in files) {
28 | const file = files[key];
29 | const filePath = path.join(tmpdir, file.name);
30 | const reader = fs.createReadStream(file.path);
31 | const writer = fs.createWriteStream(filePath);
32 | reader.pipe(writer);
33 | filePaths.push(filePath);
34 | }
35 |
36 | ctx.body = filePaths;
37 | });
38 |
39 | if (!module.parent) app.listen(3000);
40 |
41 | function uid() {
42 | return Math.random().toString(36).slice(2);
43 | }
44 |
--------------------------------------------------------------------------------
/body-parsing/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Body Parsing', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('POST /uppercase', function() {
11 | describe('with JSON', function() {
12 | it('should work', function(done) {
13 | request
14 | .post('/uppercase')
15 | .send({ name: 'tobi' })
16 | .expect(200)
17 | .expect({ name: 'TOBI' }, done);
18 | });
19 | });
20 |
21 | describe('with urlencoded', function() {
22 | it('should work', function(done) {
23 | request
24 | .post('/uppercase')
25 | .send('name=tj')
26 | .expect(200)
27 | .expect({ name: 'TJ' }, done);
28 | });
29 | });
30 |
31 | describe('when length > limit', function() {
32 | it('should 413', function(done) {
33 | request
34 | .post('/json')
35 | .send({ name: Array(5000).join('a') })
36 | .expect(413, done);
37 | });
38 | });
39 |
40 | describe('when no name is sent', function() {
41 | it('should 400', function(done) {
42 | request
43 | .post('/uppsercase')
44 | .send('age=10')
45 | .expect(400, done);
46 | });
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/compose/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Each `app.use()` only accepts a single generator function.
3 | * If you want to combine multiple generator functions into a single one,
4 | * you can use `koa-compose` to do so.
5 | * This allows you to use `app.use()` only once.
6 | * Your code will end up looking something like:
7 | *
8 | * app.use(compose([
9 | * function *(){},
10 | * function *(){},
11 | * function *(){}
12 | * ]))
13 | */
14 |
15 | const compose = require('koa-compose');
16 | const Koa = require('koa');
17 | const app = module.exports = new Koa();
18 |
19 | // x-response-time
20 |
21 | async function responseTime(ctx, next) {
22 | const start = new Date();
23 | await next();
24 | const ms = new Date() - start;
25 | ctx.set('X-Response-Time', ms + 'ms');
26 | }
27 |
28 | // logger
29 |
30 | async function logger(ctx, next) {
31 | const start = new Date();
32 | await next();
33 | const ms = new Date() - start;
34 | if ('test' != process.env.NODE_ENV) {
35 | console.log('%s %s - %s', ctx.method, ctx.url, ms);
36 | }
37 | }
38 |
39 | // response
40 |
41 | async function respond(ctx, next) {
42 | await next();
43 | if ('/' != ctx.url) return;
44 | ctx.body = 'Hello World';
45 | }
46 |
47 | // composed middleware
48 |
49 | const all = compose([
50 | responseTime,
51 | logger,
52 | respond
53 | ]);
54 |
55 | app.use(all);
56 |
57 | if (!module.parent) app.listen(3000);
58 |
--------------------------------------------------------------------------------
/blog/app.js:
--------------------------------------------------------------------------------
1 |
2 | const render = require('./lib/render');
3 | const logger = require('koa-logger');
4 | const router = require('@koa/router')();
5 | const koaBody = require('koa-body');
6 |
7 | const Koa = require('koa');
8 | const app = module.exports = new Koa();
9 |
10 | // "database"
11 |
12 | const posts = [];
13 |
14 | // middleware
15 |
16 | app.use(logger());
17 |
18 | app.use(render);
19 |
20 | app.use(koaBody());
21 |
22 | // route definitions
23 |
24 | router.get('/', list)
25 | .get('/post/new', add)
26 | .get('/post/:id', show)
27 | .post('/post', create);
28 |
29 | app.use(router.routes());
30 |
31 | /**
32 | * Post listing.
33 | */
34 |
35 | async function list(ctx) {
36 | await ctx.render('list', { posts: posts });
37 | }
38 |
39 | /**
40 | * Show creation form.
41 | */
42 |
43 | async function add(ctx) {
44 | await ctx.render('new');
45 | }
46 |
47 | /**
48 | * Show post :id.
49 | */
50 |
51 | async function show(ctx) {
52 | const id = ctx.params.id;
53 | const post = posts[id];
54 | if (!post) ctx.throw(404, 'invalid post id');
55 | await ctx.render('show', { post: post });
56 | }
57 |
58 | /**
59 | * Create a post.
60 | */
61 |
62 | async function create(ctx) {
63 | const post = ctx.request.body;
64 | const id = posts.push(post) - 1;
65 | post.created_at = new Date();
66 | post.id = id;
67 | ctx.redirect('/');
68 | }
69 |
70 | // listen
71 |
72 | if (!module.parent) app.listen(3000);
73 |
--------------------------------------------------------------------------------
/stream-file/README.md:
--------------------------------------------------------------------------------
1 | # stream-file
2 |
3 | Stream a file from the local directory. To invoke, the following command begins listening on localhost:3000.
4 |
5 | node --harmony app.js
6 |
7 | To see results:
8 |
9 | http://localhost:3000/README.md or
10 | http://localhost:3000/other-file-in-the-directory
11 |
12 | ## Interesting points
13 |
14 | 1. The stat() function at the bottom of app.js returns another function that will call the normal fs.stat() to get information about the named file. (The function stat() is a promise - it will ultimately return a value, although it may take a while.)
15 | 2. When any program *yields* to a function, it pauses while that function proceeds asynchronously, and eventually returns a value. When the function returns, the program resumes at that point.
16 | 3. In the example, app.use() starts everything off with `fstat = yield stat(fpath)`. We say it "yields to the stat() function." That is, app.use() pauses while the stat() function begins to execute (asynchronously), and the node interpreter goes off to work on other tasks. When the fs.stat() call completes and returns a value, app.use() resumes, and sets the value of `fstat` to the value returned by stat().
17 | 4. This example also uses the createReadStream() function to create a stream which is another way to handle data (asynchronously) from a file.
18 | 5. `this.body` gets the result of the fs.createReadStream() (which is the stream's data) and sends it to the web browser client that has connected in to the URL above.
19 |
--------------------------------------------------------------------------------
/csrf/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const app = require('./app');
3 | const server = app.listen();
4 | const request = require('supertest').agent(server);
5 |
6 | let token;
7 | let cookie;
8 |
9 | describe('csrf', function() {
10 | after(function() {
11 | server.close();
12 | });
13 |
14 | describe('GET /token', function() {
15 | it('should get token', function(done) {
16 | request
17 | .get('/token')
18 | .expect(200)
19 | .end(function(err, res) {
20 | token = res.text;
21 | token.should.be.String;
22 | cookie = res.headers['set-cookie'].join(';');
23 | done(err);
24 | });
25 | });
26 | });
27 |
28 | describe('POST /post', function() {
29 | it('should 403 without token', function(done) {
30 | request
31 | .post('/post')
32 | .send({foo: 'bar'})
33 | .expect(403, done);
34 | });
35 |
36 | it('should 403 with wrong token', function(done) {
37 | request
38 | .post('/post')
39 | .send({foo: 'bar'})
40 | .set('x-csrf-token', 'wrong token')
41 | .expect(403, done);
42 | });
43 |
44 | it('should 200 with token in head', function(done) {
45 | request
46 | .post('/post')
47 | .set('Cookie', cookie)
48 | .set('x-csrf-token', token)
49 | .send({foo: 'bar'})
50 | .expect(200, done);
51 | });
52 |
53 | it('should 200 with token in body', function(done) {
54 | request
55 | .post('/post')
56 | .set('Cookie', cookie)
57 | .send({_csrf: token})
58 | .expect(200, done);
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/vhost/app.js:
--------------------------------------------------------------------------------
1 | const compose = require('koa-compose');
2 | const Koa = require('koa');
3 | const app = module.exports = new Koa();
4 |
5 | // virtual host apps
6 |
7 | const wwwSubdomain = composer(require('./apps/koa'));
8 | const barSubdomain = composer(require('./apps/array'));
9 |
10 | // compose koa apps and middleware arrays
11 | // to be used later in our host switch generator
12 |
13 | function composer(app) {
14 | const middleware = app instanceof Koa ? app.middleware : app;
15 | return compose(middleware);
16 | }
17 |
18 | // look ma, global response logging for all our apps!
19 |
20 | app.use(async function(ctx, next) {
21 | const start = new Date();
22 | await next();
23 | const ms = new Date() - start;
24 | if ('test' != process.env.NODE_ENV) {
25 | console.log('%s %s %s - %sms', ctx.host, ctx.method, ctx.url, ms);
26 | }
27 | });
28 |
29 | // switch between appropriate hosts calling their
30 | // composed middleware with the appropriate context.
31 |
32 | app.use(async function(ctx, next) {
33 | switch (ctx.hostname) {
34 | case 'example.com':
35 | case 'www.example.com':
36 | // displays `Hello from main app`
37 | // and sets a `X-Custom` response header
38 | return await wwwSubdomain.apply(this, [ctx, next]);
39 | case 'bar.example.com':
40 | // displays `Howzit? From bar middleware bundle`
41 | // and sets a `X-Response-Time` response header
42 | return await barSubdomain.apply(this, [ctx, next]);
43 | }
44 |
45 | // everything else, eg: 127.0.0.1:3000
46 | // will propagate to 404 Not Found
47 | return await next();
48 | });
49 |
50 | if (!module.parent) app.listen(3000);
51 |
--------------------------------------------------------------------------------
/blog/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const app = require('./app');
3 | const server = app.listen();
4 | const request = require('supertest').agent(server);
5 |
6 | describe('Blog', function() {
7 | after(function() {
8 | server.close();
9 | });
10 |
11 | describe('GET /', function() {
12 | it('should see title "Posts"', function(done) {
13 | request
14 | .get('/')
15 | .expect(200, function(err, res) {
16 | if (err) return done(err);
17 | res.should.be.html;
18 | res.text.should.include('Posts');
19 | done();
20 | });
21 | });
22 |
23 | it('should see 0 post', function(done) {
24 | request
25 | .get('/')
26 | .expect(200, function(err, res) {
27 | if (err) return done(err);
28 |
29 | res.should.be.html;
30 | res.text.should.include('You have 0 posts!
');
31 | done();
32 | });
33 | });
34 | });
35 |
36 | describe('POST /post/new', function() {
37 | it('should create post and redirect to /', function(done) {
38 | request
39 | .post('/post')
40 | .send({title: 'Title', body: 'Contents'})
41 | .end(function(err, res) {
42 | if (err) return done(err);
43 |
44 | res.header.location.should.be.equal('/');
45 | done();
46 | });
47 | });
48 | });
49 |
50 | describe('GET /post/0', function() {
51 | it('should see post', function(done) {
52 | request
53 | .get('/post/0')
54 | .expect(200, function(err, res) {
55 | if (err) return done(err);
56 |
57 | res.should.be.html;
58 | res.text.should.include('Title
');
59 | res.text.should.include('Contents
');
60 | done();
61 | });
62 | });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/negotiation/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('negotiation', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('json', function() {
11 | it('should respond with json', function(done) {
12 | request
13 | .get('/tobi')
14 | .set('Accept', 'application/json')
15 | .expect(200)
16 | .expect('Content-Type', /json/)
17 | .expect({ name: 'tobi', species: 'ferret' }, done);
18 | });
19 | });
20 |
21 | describe('xml', function() {
22 | it('should respond with xml', function(done) {
23 | request
24 | .get('/tobi')
25 | .set('Accept', 'application/xml')
26 | .expect(200)
27 | .expect('Content-Type', /xml/)
28 | .expect('tobi', done);
29 | });
30 | });
31 |
32 | describe('html', function() {
33 | it('should respond with html', function(done) {
34 | request
35 | .get('/tobi')
36 | .set('Accept', 'text/html')
37 | .expect(200)
38 | .expect('Content-Type', /html/)
39 | .expect('tobi
', done);
40 | });
41 | });
42 |
43 | describe('text', function() {
44 | it('should respond with html', function(done) {
45 | request
46 | .get('/tobi')
47 | .set('Accept', 'text/plain')
48 | .expect(200)
49 | .expect('Content-Type', /plain/)
50 | .expect('tobi', done);
51 | });
52 | });
53 |
54 | describe('*/*', function() {
55 | it('should give precedence to the first accepted type', function(done) {
56 | request
57 | .get('/tobi')
58 | .set('Accept', '*/*')
59 | .expect(200)
60 | .expect('Content-Type', /json/)
61 | .expect('{"name":"tobi","species":"ferret"}', done);
62 | });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/negotiation/app.js:
--------------------------------------------------------------------------------
1 |
2 | const Koa = require('koa');
3 | const app = module.exports = new Koa();
4 |
5 | const tobi = {
6 | _id: '123',
7 | name: 'tobi',
8 | species: 'ferret'
9 | };
10 |
11 | const loki = {
12 | _id: '321',
13 | name: 'loki',
14 | species: 'ferret'
15 | };
16 |
17 | const users = {
18 | tobi: tobi,
19 | loki: loki
20 | };
21 |
22 | // content negotiation middleware.
23 | // note that you should always check for
24 | // presence of a body, and sometimes you
25 | // may want to check the type, as it may
26 | // be a stream, buffer, string, etc.
27 |
28 | app.use(async function(ctx, next) {
29 | await next();
30 |
31 | // no body? nothing to format, early return
32 | if (!ctx.body) return;
33 |
34 | // Check which type is best match by giving
35 | // a list of acceptable types to `req.accepts()`.
36 | const type = ctx.accepts('json', 'html', 'xml', 'text');
37 |
38 | // not acceptable
39 | if (type === false) ctx.throw(406);
40 |
41 | // accepts json, koa handles this for us,
42 | // so just return
43 | if (type === 'json') return;
44 |
45 | // accepts xml
46 | if (type === 'xml') {
47 | ctx.type = 'xml';
48 | ctx.body = '' + ctx.body.name + '';
49 | return;
50 | }
51 |
52 | // accepts html
53 | if (type === 'html') {
54 | ctx.type = 'html';
55 | ctx.body = '' + ctx.body.name + '
';
56 | return;
57 | }
58 |
59 | // default to text
60 | ctx.type = 'text';
61 | ctx.body = ctx.body.name;
62 | });
63 |
64 | // filter responses, in this case remove ._id
65 | // since it's private
66 |
67 | app.use(async function(ctx, next) {
68 | await next();
69 |
70 | if (!ctx.body) return;
71 |
72 | delete ctx.body._id;
73 | });
74 |
75 | // try $ GET /tobi
76 | // try $ GET /loki
77 |
78 | app.use(async function(ctx) {
79 | const name = ctx.path.slice(1);
80 | const user = users[name];
81 | ctx.body = user;
82 | });
83 |
84 | if (!module.parent) app.listen(3000);
85 |
--------------------------------------------------------------------------------
/vhost/test.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const server = app.listen();
3 | const request = require('supertest').agent(server);
4 |
5 | describe('Virtual Host', function() {
6 | after(function() {
7 | server.close();
8 | });
9 |
10 | describe('www subdomain koa app', function() {
11 | describe('when GET /', function() {
12 | it('should say "Hello from www app"', function(done) {
13 | request
14 | .get('/')
15 | .set('Host', 'www.example.com')
16 | .expect(200)
17 | .expect('Hello from www app', done);
18 | });
19 |
20 | it('should set X-Custom', function(done) {
21 | request
22 | .get('/')
23 | .set('Host', 'www.example.com')
24 | .expect('X-Custom', 'Dub Dub Dub App')
25 | .expect(200, done);
26 | });
27 | });
28 |
29 | describe('when GET / without subdomain', function() {
30 | it('should say "Hello from www app"', function(done) {
31 | request
32 | .get('/')
33 | .set('Host', 'example.com')
34 | .expect(200)
35 | .expect('Hello from www app', done);
36 | });
37 |
38 | it('should set X-Custom', function(done) {
39 | request
40 | .get('/')
41 | .set('Host', 'example.com')
42 | .expect('X-Custom', 'Dub Dub Dub App')
43 | .expect(200, done);
44 | });
45 | });
46 |
47 | describe('when not GET /', function() {
48 | it('should 404', function(done) {
49 | request
50 | .get('/aklsjdf')
51 | .set('Host', 'example.com')
52 | .expect(404, done);
53 | });
54 | });
55 | });
56 | describe('bar subdomain array bundle', function() {
57 | describe('when GET /', function() {
58 | it('should say "Howzit? From bar middleware bundle"', function(done) {
59 | request
60 | .get('/')
61 | .set('Host', 'bar.example.com')
62 | .expect(200)
63 | .expect('Howzit? From bar middleware bundle', done);
64 | });
65 |
66 | it('should set X-Response-Time', function(done) {
67 | request
68 | .get('/')
69 | .set('Host', 'bar.example.com')
70 | .expect('X-Response-Time', /ms$/)
71 | .expect(200, done);
72 | });
73 | });
74 |
75 | describe('when not GET /', function() {
76 | it('should 404', function(done) {
77 | request
78 | .get('/aklsjdf')
79 | .set('Host', 'bar.example.com')
80 | .expect(404, done);
81 | });
82 | });
83 | });
84 | describe('default vhost', function() {
85 | describe('when GET /', function() {
86 | it('should 404', function(done) {
87 | request
88 | .get('/')
89 | .set('Host', '127.0.0.1')
90 | .expect(404, done);
91 | });
92 | });
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/multipart/test.js:
--------------------------------------------------------------------------------
1 | require('should');
2 | const fs = require('fs');
3 | const app = require('./app');
4 | const server = app.listen();
5 | const request = require('supertest').agent(server);
6 |
7 | // https://github.com/mscdex/busboy/blob/master/test/test-types-multipart.js
8 | const ct = 'multipart/form-data; boundary=---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k';
9 | const body = [
10 | '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
11 | 'Content-Disposition: form-data; name="file_name_0"',
12 | '',
13 | 'super alpha file',
14 | '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
15 | 'Content-Disposition: form-data; name="file_name_1"',
16 | '',
17 | 'super beta file',
18 | '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
19 | 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
20 | 'Content-Type: application/octet-stream',
21 | '',
22 | 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
23 | '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
24 | 'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"',
25 | 'Content-Type: application/octet-stream',
26 | '',
27 | 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
28 | '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
29 | ].join('\r\n');
30 |
31 | describe('Multipart Files', function() {
32 | after(function() {
33 | server.close();
34 | });
35 |
36 | it('should store all the files', function(done) {
37 | request
38 | .post('/')
39 | .set('Content-Type', ct)
40 | .send(body)
41 | .expect(200)
42 | .end(function(err, res) {
43 | if (err) return done(err);
44 |
45 | const files = res.body;
46 | files.should.have.length(2);
47 | fs.stat(files[0], function(err) {
48 | if (err) return done(err);
49 |
50 | fs.stat(files[1], done);
51 | });
52 | });
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Koa Examples
2 |
3 | A repository containing small examples to illustrate the use of Koa
4 | for creating web applications and other HTTP servers.
5 |
6 | # Running tests
7 |
8 | ```bash
9 | npm test
10 | npm run lint
11 | ```
12 |
13 | ## Included Examples
14 |
15 | - [404](404) - 404 handling
16 | - [base-auth](base-auth) - middleware base auth example
17 | - [blog](blog) - multi-route & view rendering
18 | - [body-parsing](body-parsing) - request body parsing
19 | - [compose](compose) - compose middlewares example
20 | - [conditional-middleware](conditional-middleware) - shows how middleware may be conditionally applied
21 | - [cookies](cookies) - cookie usage example
22 | - [csrf](csrf) - middleware csrf example
23 | - [errors](errors) - error handling & propagation
24 | - [flash-messages](flash-messages) - flash example
25 | - [hello-world](hello-world) - hello world application
26 | - [multipart](multipart) - multipart example downloading files using co-busboy
27 | - [negotiation](negotiation) - negotiation usage example
28 | - [stream-file](stream-file) - simple file streaming
29 | - [stream-objects](stream-objects) - objects streaming
30 | - [stream-server-side-events](stream-server-side-events) - server side events streaming
31 | - [stream-view](stream-view) - view streaming
32 | - [templates](templates) - simple view rendering
33 | - [upload](upload) - multi-file uploading
34 | - [vhost](vhost) - virtual host example
35 |
36 | ## Example Repositories
37 |
38 | - [coko](https://github.com/bhaskarmelkani/coko) - A minimal convention over configuration framework/boilerplate for Koa 2.
39 | - [kails](https://github.com/embbnux/kails) - A Web App like Rails build with Koa v2, Webpack and Postgres
40 | - [muffin](https://github.com/muffinjs/server) - A content management system build on top of Koa v2
41 | - [links](https://github.com/juliangruber/links) - experimental content sharing and collaboration platform
42 | - [component-crawler](https://github.com/component/crawler.js) - crawl users and organizations for repositories with `component.json`s
43 | - [bigpipe](https://github.com/jonathanong/bigpipe-example) - Facebook's BigPipe implementation in koa and component
44 | - [webcam-mjpeg-stream](https://github.com/jonathanong/webcam-mjpeg-stream) - stream JPEG snapshots from your Mac
45 | - [cnpmjs.org](https://github.com/cnpm/cnpmjs.org) - Private npm registry and web for Enterprise, base on koa, MySQL and Simple Store Service
46 | - [blog-mongo](https://github.com/marcusoftnet/koablog-mongo) - the blog example from this repo, but using a MongoDb database, and tests
47 | - [koa-rest](https://github.com/hemanth/koa-rest) - A simple app to demo REST API
48 | - [koajs-rest-skeleton](https://github.com/ria-com/node-koajs-rest-skeleton) - A simple Koa REST Skeleton Application
49 | - [koa-bookshelf](https://github.com/Tomsqualm/koa-bookshelf) - Koa example with CRUD, using MongoDB and Heroku comptability
50 | - [todo](https://github.com/koajs/todo) - A todo example written in koa and [react](http://facebook.github.io/react/)
51 | - [koa-skeleton](https://github.com/danneu/koa-skeleton) - A simple made-to-be-forked Koa app that uses Postgres and deploys to Heroku.
52 | - Live demo:
53 | - [nodejs-docs-samples](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/appengine/koa) - An example Koa app and tutorial for deploying to Google App Engine
54 | - Live demo:
55 | - [koa-passport-mongoose-graphql](https://github.com/sibeliusseraphini/koa-passport-mongoose-graphql) - Koa 2 starterkit using mongoose, graphql setup, and authentication with passport
56 | - [hacknical](https://github.com/ecmadao/hacknical) - A website for github user to make a better resume, based on Koa v2, redis and mongoose.
57 | - [koa-vue-notes-api](https://github.com/johndatserakis/koa-vue-notes-api) - A fleshed-out SPA using Koa 2.3 on the backend and Vue 2.4 on the frontend. Includes fully featured user-authentication components, CRUD actions for the user's notes, and async/await.
58 | - [koa-typescript-node](https://github.com/Talento90/typescript-node) - Template for building nodejs and typescript services. Features: MySql, Migrations, Docker, Unit & Integration Tests, JWT authentication, authorization, graceful shutdown, Prettier.
59 | - [koa-shell](https://github.com/lifeeka/koa-shell) - Structured sample skeleton application for microservices and api development with Koa.
60 |
61 | ## Boilerplates
62 |
63 | - [koa2-boilerplate](https://github.com/geekplux/koa2-boilerplate) - A minimal boilerplate of koa v2 development
64 | - [api-boilerplate](https://github.com/koajs/api-boilerplate) - API application boilerplate
65 | - [component-koa-et-al-boilerplate](https://github.com/sunewt/component-koa-et-al-boilerplate) - Server/client boilerplate with component, livereload, and more
66 | - [koa-typescript-starter](https://github.com/ddimaria/koa-typescript-starter) - A Koa2 starter kit using TypeScript, ES6 imports/exports, Travis, Coveralls, Jasmine, Chai, Istanbul/NYC, Lodash, Nodemon, Docker, & Swagger
67 |
68 | ## Yeoman Generators
69 | - [koa-rest](https://github.com/PatrickWolleb/generator-koa-rest) - RESTful API scaffolder with subgenerator
70 | - [koa](https://github.com/peter-vilja/generator-koa) - Web Application scaffolder
71 | - [k](https://github.com/minghe/generator-k) - Web Application scaffolder with Chinese README
72 |
73 | ## Articles
74 |
75 | - [Building a RESTful API with Koa and Postgres](http://mherman.org/blog/2017/08/23/building-a-restful-api-with-koa-and-postgres)
76 | - [User Authentication with Passport and Koa](http://mherman.org/blog/2018/01/02/user-authentication-with-passport-and-koa)
77 |
--------------------------------------------------------------------------------