Repository: koajs/examples Branch: master Commit: 40c77dbecec6 Files: 62 Total size: 50.1 KB Directory structure: gitextract_ti2c8rw1/ ├── .editorconfig ├── .eslintrc.yml ├── .gitignore ├── .jshintignore ├── .travis.yml ├── 404/ │ ├── app.js │ └── test.js ├── Readme.md ├── base-auth/ │ ├── app.js │ └── test.js ├── blog/ │ ├── app.js │ ├── lib/ │ │ └── render.js │ ├── test.js │ └── views/ │ ├── index.html │ ├── layout.html │ ├── list.html │ ├── new.html │ └── show.html ├── body-parsing/ │ ├── app.js │ └── test.js ├── compose/ │ ├── app.js │ └── test.js ├── conditional-middleware/ │ └── app.js ├── cookies/ │ ├── app.js │ └── test.js ├── csrf/ │ ├── app.js │ └── test.js ├── errors/ │ ├── app.js │ └── test.js ├── flash-messages/ │ ├── app.js │ └── test.js ├── hello-world/ │ ├── app.js │ └── test.js ├── multipart/ │ ├── app.js │ └── test.js ├── negotiation/ │ ├── app.js │ └── test.js ├── package.json ├── stream-file/ │ ├── README.md │ ├── app.js │ └── test.js ├── stream-objects/ │ ├── README.md │ ├── app.js │ └── test.js ├── stream-server-side-events/ │ ├── README.md │ ├── app.js │ ├── db.js │ └── sse.js ├── stream-view/ │ ├── README.md │ ├── app.js │ ├── test.js │ └── view.js ├── templates/ │ ├── app.js │ ├── test.js │ └── views/ │ └── user.ejs ├── upload/ │ ├── app.js │ └── public/ │ ├── 404.html │ └── index.html └── vhost/ ├── app.js ├── apps/ │ ├── array.js │ └── koa.js └── test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false [Makefile] indent_style = tab ================================================ FILE: .eslintrc.yml ================================================ env: mocha: true extends: koa rules: no-var: 0 prefer-arrow-callback: 0 ================================================ FILE: .gitignore ================================================ # Compiled source # ################### *.com *.class *.dll *.exe *.o *.so # Packages # ############ # it's better to unpack these files and commit the raw source # git has its own built in compression methods *.7z *.dmg *.gz *.iso *.jar *.rar *.tar *.zip # Logs and databases # ###################### *.log *.sql *.sqlite # OS generated files # ###################### .DS_Store* ehthumbs.db Icon? Thumbs.db .idea # Node.js # ########### lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz pids logs results node_modules npm-debug.log # Components # ############## /build /components # ImageMagick # ############### *.cache *.mpc # Other # ######### test/*.2 test/*/*.2 test/*.mp4 test/images/originalSideways.jpg.2 # Traceur output # ################## out/* ================================================ FILE: .jshintignore ================================================ node_modules */test.js ================================================ FILE: .travis.yml ================================================ node_js: - stable - 8 language: node_js script: - npm run lint - npm test ================================================ FILE: 404/app.js ================================================ const Koa = require('koa'); const app = module.exports = new Koa(); app.use(async function pageNotFound(ctx) { // we need to explicitly set 404 here // so that koa doesn't assign 200 on body= ctx.status = 404; switch (ctx.accepts('html', 'json')) { case 'html': ctx.type = 'html'; ctx.body = '

Page Not Found

'; break; case 'json': ctx.body = { message: 'Page Not Found' }; break; default: ctx.type = 'text'; ctx.body = 'Page Not Found'; } }); if (!module.parent) app.listen(3000); ================================================ FILE: 404/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('404', function() { after(function() { server.close(); }); describe('when GET /', function() { it('should return the 404 page', function(done) { request .get('/') .expect(404) .expect(/Page Not Found/, done); }); }); }); ================================================ FILE: Readme.md ================================================ # Koa Examples A repository containing small examples to illustrate the use of Koa for creating web applications and other HTTP servers. # Running tests ```bash npm test npm run lint ``` ## Included Examples - [404](404) - 404 handling - [base-auth](base-auth) - middleware base auth example - [blog](blog) - multi-route & view rendering - [body-parsing](body-parsing) - request body parsing - [compose](compose) - compose middlewares example - [conditional-middleware](conditional-middleware) - shows how middleware may be conditionally applied - [cookies](cookies) - cookie usage example - [csrf](csrf) - middleware csrf example - [errors](errors) - error handling & propagation - [flash-messages](flash-messages) - flash example - [hello-world](hello-world) - hello world application - [multipart](multipart) - multipart example downloading files using co-busboy - [negotiation](negotiation) - negotiation usage example - [stream-file](stream-file) - simple file streaming - [stream-objects](stream-objects) - objects streaming - [stream-server-side-events](stream-server-side-events) - server side events streaming - [stream-view](stream-view) - view streaming - [templates](templates) - simple view rendering - [upload](upload) - multi-file uploading - [vhost](vhost) - virtual host example ## Example Repositories - [coko](https://github.com/bhaskarmelkani/coko) - A minimal convention over configuration framework/boilerplate for Koa 2. - [kails](https://github.com/embbnux/kails) - A Web App like Rails build with Koa v2, Webpack and Postgres - [muffin](https://github.com/muffinjs/server) - A content management system build on top of Koa v2 - [links](https://github.com/juliangruber/links) - experimental content sharing and collaboration platform - [component-crawler](https://github.com/component/crawler.js) - crawl users and organizations for repositories with `component.json`s - [bigpipe](https://github.com/jonathanong/bigpipe-example) - Facebook's BigPipe implementation in koa and component - [webcam-mjpeg-stream](https://github.com/jonathanong/webcam-mjpeg-stream) - stream JPEG snapshots from your Mac - [cnpmjs.org](https://github.com/cnpm/cnpmjs.org) - Private npm registry and web for Enterprise, base on koa, MySQL and Simple Store Service - [blog-mongo](https://github.com/marcusoftnet/koablog-mongo) - the blog example from this repo, but using a MongoDb database, and tests - [koa-rest](https://github.com/hemanth/koa-rest) - A simple app to demo REST API - [koajs-rest-skeleton](https://github.com/ria-com/node-koajs-rest-skeleton) - A simple Koa REST Skeleton Application - [koa-bookshelf](https://github.com/Tomsqualm/koa-bookshelf) - Koa example with CRUD, using MongoDB and Heroku comptability - [todo](https://github.com/koajs/todo) - A todo example written in koa and [react](http://facebook.github.io/react/) - [koa-skeleton](https://github.com/danneu/koa-skeleton) - A simple made-to-be-forked Koa app that uses Postgres and deploys to Heroku. - Live demo: - [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 - Live demo: - [koa-passport-mongoose-graphql](https://github.com/sibeliusseraphini/koa-passport-mongoose-graphql) - Koa 2 starterkit using mongoose, graphql setup, and authentication with passport - [hacknical](https://github.com/ecmadao/hacknical) - A website for github user to make a better resume, based on Koa v2, redis and mongoose. - [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. - [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. - [koa-shell](https://github.com/lifeeka/koa-shell) - Structured sample skeleton application for microservices and api development with Koa. ## Boilerplates - [koa2-boilerplate](https://github.com/geekplux/koa2-boilerplate) - A minimal boilerplate of koa v2 development - [api-boilerplate](https://github.com/koajs/api-boilerplate) - API application boilerplate - [component-koa-et-al-boilerplate](https://github.com/sunewt/component-koa-et-al-boilerplate) - Server/client boilerplate with component, livereload, and more - [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 ## Yeoman Generators - [koa-rest](https://github.com/PatrickWolleb/generator-koa-rest) - RESTful API scaffolder with subgenerator - [koa](https://github.com/peter-vilja/generator-koa) - Web Application scaffolder - [k](https://github.com/minghe/generator-k) - Web Application scaffolder with Chinese README ## Articles - [Building a RESTful API with Koa and Postgres](http://mherman.org/blog/2017/08/23/building-a-restful-api-with-koa-and-postgres) - [User Authentication with Passport and Koa](http://mherman.org/blog/2018/01/02/user-authentication-with-passport-and-koa) ================================================ FILE: base-auth/app.js ================================================ const Koa = require('koa'); const auth = require('koa-basic-auth'); const app = module.exports = new Koa(); // custom 401 handling app.use(async function(ctx, next) { try { await next(); } catch (err) { if (err.status === 401) { ctx.status = 401; ctx.set('WWW-Authenticate', 'Basic'); ctx.body = 'cant haz that'; } else { throw err; } } }); // require auth app.use(auth({ name: 'tj', pass: 'tobi' })); // secret response app.use(async function(ctx) { ctx.body = 'secret'; }); if (!module.parent) app.listen(3000); ================================================ FILE: base-auth/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Koa Basic Auth', function() { after(function() { server.close(); }); describe('with no credentials', function() { it('should `throw` 401', function(done) { request .get('/') .expect(401, done); }); }); describe('with invalid credentials', function() { it('should `throw` 401', function(done) { request .get('/') .auth('user', 'invalid password') .expect(401, done); }); }); describe('with valid credentials', function() { it('should call the next middleware', function(done) { request .get('/') .auth('tj', 'tobi') .expect(200) .expect('secret', done); }); }); }); ================================================ FILE: blog/app.js ================================================ const render = require('./lib/render'); const logger = require('koa-logger'); const router = require('@koa/router')(); const koaBody = require('koa-body'); const Koa = require('koa'); const app = module.exports = new Koa(); // "database" const posts = []; // middleware app.use(logger()); app.use(render); app.use(koaBody()); // route definitions router.get('/', list) .get('/post/new', add) .get('/post/:id', show) .post('/post', create); app.use(router.routes()); /** * Post listing. */ async function list(ctx) { await ctx.render('list', { posts: posts }); } /** * Show creation form. */ async function add(ctx) { await ctx.render('new'); } /** * Show post :id. */ async function show(ctx) { const id = ctx.params.id; const post = posts[id]; if (!post) ctx.throw(404, 'invalid post id'); await ctx.render('show', { post: post }); } /** * Create a post. */ async function create(ctx) { const post = ctx.request.body; const id = posts.push(post) - 1; post.created_at = new Date(); post.id = id; ctx.redirect('/'); } // listen if (!module.parent) app.listen(3000); ================================================ FILE: blog/lib/render.js ================================================ /** * Module dependencies. */ const views = require('koa-views'); const path = require('path'); // setup views mapping .html // to the swig template engine module.exports = views(path.join(__dirname, '/../views'), { map: { html: 'swig' } }); ================================================ FILE: blog/test.js ================================================ require('should'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Blog', function() { after(function() { server.close(); }); describe('GET /', function() { it('should see title "Posts"', function(done) { request .get('/') .expect(200, function(err, res) { if (err) return done(err); res.should.be.html; res.text.should.include('Posts'); done(); }); }); it('should see 0 post', function(done) { request .get('/') .expect(200, function(err, res) { if (err) return done(err); res.should.be.html; res.text.should.include('

You have 0 posts!

'); done(); }); }); }); describe('POST /post/new', function() { it('should create post and redirect to /', function(done) { request .post('/post') .send({title: 'Title', body: 'Contents'}) .end(function(err, res) { if (err) return done(err); res.header.location.should.be.equal('/'); done(); }); }); }); describe('GET /post/0', function() { it('should see post', function(done) { request .get('/post/0') .expect(200, function(err, res) { if (err) return done(err); res.should.be.html; res.text.should.include('

Title

'); res.text.should.include('

Contents

'); done(); }); }); }); }); ================================================ FILE: blog/views/index.html ================================================ Blog {% for post in posts %} {% endfor %} ================================================ FILE: blog/views/layout.html ================================================ {% block title %}Blog{% endblock %}
{% block content %}

Missing content!

{% endblock %}
================================================ FILE: blog/views/list.html ================================================ {% extends 'layout.html' %} {% block title %}Posts{% endblock %} {% block content %}

Posts

You have {{ posts.length }} posts!

Create a Post

{% endblock %} ================================================ FILE: blog/views/new.html ================================================ {% extends 'layout.html' %} {% block title %}New Post{% endblock %} {% block content %}

New Post

Create a new post.

{% endblock %} ================================================ FILE: blog/views/show.html ================================================ {% extends 'layout.html' %} {% block title %}{{ post.title }}{% endblock %} {% block content %}

{{ post.title }}

{{ post.body }}

{% endblock %} ================================================ FILE: body-parsing/app.js ================================================ const Koa = require('koa'); const koaBody = require('koa-body'); const app = module.exports = new Koa(); app.use(koaBody({ jsonLimit: '1kb' })); // POST .name to /uppercase // co-body accepts application/json // and application/x-www-form-urlencoded app.use(async function(ctx) { const body = ctx.request.body; if (!body.name) ctx.throw(400, '.name required'); ctx.body = { name: body.name.toUpperCase() }; }); if (!module.parent) app.listen(3000); ================================================ FILE: body-parsing/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Body Parsing', function() { after(function() { server.close(); }); describe('POST /uppercase', function() { describe('with JSON', function() { it('should work', function(done) { request .post('/uppercase') .send({ name: 'tobi' }) .expect(200) .expect({ name: 'TOBI' }, done); }); }); describe('with urlencoded', function() { it('should work', function(done) { request .post('/uppercase') .send('name=tj') .expect(200) .expect({ name: 'TJ' }, done); }); }); describe('when length > limit', function() { it('should 413', function(done) { request .post('/json') .send({ name: Array(5000).join('a') }) .expect(413, done); }); }); describe('when no name is sent', function() { it('should 400', function(done) { request .post('/uppsercase') .send('age=10') .expect(400, done); }); }); }); }); ================================================ FILE: compose/app.js ================================================ /** * Each `app.use()` only accepts a single generator function. * If you want to combine multiple generator functions into a single one, * you can use `koa-compose` to do so. * This allows you to use `app.use()` only once. * Your code will end up looking something like: * * app.use(compose([ * function *(){}, * function *(){}, * function *(){} * ])) */ const compose = require('koa-compose'); const Koa = require('koa'); const app = module.exports = new Koa(); // x-response-time async function responseTime(ctx, next) { const start = new Date(); await next(); const ms = new Date() - start; ctx.set('X-Response-Time', ms + 'ms'); } // logger async function logger(ctx, next) { const start = new Date(); await next(); const ms = new Date() - start; if ('test' != process.env.NODE_ENV) { console.log('%s %s - %s', ctx.method, ctx.url, ms); } } // response async function respond(ctx, next) { await next(); if ('/' != ctx.url) return; ctx.body = 'Hello World'; } // composed middleware const all = compose([ responseTime, logger, respond ]); app.use(all); if (!module.parent) app.listen(3000); ================================================ FILE: compose/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Compose', function() { after(function() { server.close(); }); describe('when GET /', function() { it('should say "Hello World"', function(done) { request .get('/') .expect(200) .expect('Hello World', done); }); it('should set X-Response-Time', function(done) { request .get('/') .expect('X-Response-Time', /ms$/) .expect(200, done); }); }); describe('when not GET /', function() { it('should 404', function(done) { request .get('/aklsjdf') .expect(404, done); }); }); }); ================================================ FILE: conditional-middleware/app.js ================================================ const logger = require('koa-logger'); const Koa = require('koa'); const app = new Koa(); // passing any middleware to this middleware // will make it conditional, and will not be used // when an asset is requested, illustrating how // middleware may "wrap" other middleware. function ignoreAssets(mw) { return async function(ctx, next) { if (/(\.js|\.css|\.ico)$/.test(ctx.path)) { await next(); } else { // must .call() to explicitly set the receiver await mw.call(this, ctx, next); } }; } // TRY: // $ curl http://localhost:3000/ // $ curl http://localhost:3000/style.css // $ curl http://localhost:3000/some.html app.use(ignoreAssets(logger())); app.use(async function(ctx) { ctx.body = 'Hello World'; }); app.listen(3000); ================================================ FILE: cookies/app.js ================================================ /** * This example simply sets the number of views from the same client * both as a cookie and as a response string. */ const Koa = require('koa'); const app = module.exports = new Koa(); app.use(async function(ctx) { const n = ~~ctx.cookies.get('view') + 1; ctx.cookies.set('view', n); ctx.body = n + ' views'; }); if (!module.parent) app.listen(3000); ================================================ FILE: cookies/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Cookies Views', function() { after(function() { server.close(); }); [1, 2, 3].forEach(function(i) { describe('on iteration #' + i, function() { it('should set the views as a cookie and as the body', function(done) { request .get('/') .expect(200) .expect('Set-Cookie', new RegExp('view=' + i)) .expect(i + ' views', done); }); }); }); }); ================================================ FILE: csrf/app.js ================================================ const Koa = require('koa'); const koaBody = require('koa-body'); const session = require('koa-session'); const CSRF = require('koa-csrf'); const router = require('@koa/router')(); const app = module.exports = new Koa(); /** * csrf need session */ app.keys = ['session key', 'csrf example']; app.use(session(app)); app.use(koaBody()); /** * maybe a bodyparser */ /** * csrf middleware */ app.use(new CSRF()); /** * route */ router.get('/token', token) .post('/post', post); app.use(router.routes()); async function token(ctx) { ctx.body = ctx.csrf; } async function post(ctx) { ctx.body = {ok: true}; } if (!module.parent) app.listen(3000); ================================================ FILE: csrf/test.js ================================================ require('should'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); let token; let cookie; describe('csrf', function() { after(function() { server.close(); }); describe('GET /token', function() { it('should get token', function(done) { request .get('/token') .expect(200) .end(function(err, res) { token = res.text; token.should.be.String; cookie = res.headers['set-cookie'].join(';'); done(err); }); }); }); describe('POST /post', function() { it('should 403 without token', function(done) { request .post('/post') .send({foo: 'bar'}) .expect(403, done); }); it('should 403 with wrong token', function(done) { request .post('/post') .send({foo: 'bar'}) .set('x-csrf-token', 'wrong token') .expect(403, done); }); it('should 200 with token in head', function(done) { request .post('/post') .set('Cookie', cookie) .set('x-csrf-token', token) .send({foo: 'bar'}) .expect(200, done); }); it('should 200 with token in body', function(done) { request .post('/post') .set('Cookie', cookie) .send({_csrf: token}) .expect(200, done); }); }); }); ================================================ FILE: errors/app.js ================================================ const Koa = require('koa'); const app = module.exports = new Koa(); // look ma, error propagation! app.use(async function(ctx, next) { try { await next(); } catch (err) { // some errors will have .status // however this is not a guarantee ctx.status = err.status || 500; ctx.type = 'html'; ctx.body = '

Something exploded, please contact Maru.

'; // since we handled this manually we'll // want to delegate to the regular app // level error handling as well so that // centralized still functions correctly. ctx.app.emit('error', err, ctx); } }); // response app.use(async function() { throw new Error('boom boom'); }); // error handler app.on('error', function(err) { if (process.env.NODE_ENV != 'test') { console.log('sent error %s to the cloud', err.message); console.log(err); } }); if (!module.parent) app.listen(3000); ================================================ FILE: errors/test.js ================================================ require('should'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Errors', function() { after(function() { server.close(); }); it('should catch the error', function(done) { request .get('/') .expect(500) .expect('Content-Type', /text\/html/, done); }); it('should emit the error on app', function(done) { app.once('error', function(err, ctx) { err.message.should.equal('boom boom'); ctx.should.be.ok; done(); }); request .get('/') .end(function() {}); }); }); ================================================ FILE: flash-messages/app.js ================================================ /** * A very simple flash example. * Only uses JSON for simplicity. */ const Koa = require('koa'); const rawBody = require('raw-body'); const session = require('koa-session'); const app = module.exports = new Koa(); // required for signed cookie sessions app.keys = ['key1', 'key2']; app.use(session(app)); app.use(async function(ctx, next) { if (ctx.method !== 'GET' || ctx.path !== '/messages') return await next(); // get any messages saved in the session const messages = ctx.session.messages || []; ctx.body = messages; // delete the messages as they've been deliverd delete ctx.session.messages; }); app.use(async function(ctx, next) { if (ctx.method !== 'POST' || ctx.path !== '/messages') return await next(); // the request string is the flash message const message = await rawBody(ctx.req, { encoding: 'utf8' }); // push the message to the session ctx.session.messages = ctx.session.messages || []; ctx.session.messages.push(message); // tell the client everything went okay ctx.status = 204; }); if (!module.parent) app.listen(3000); ================================================ FILE: flash-messages/test.js ================================================ require('should'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Flash Messages', function() { after(function() { server.close(); }); it('GET should return an empty array', function(done) { request .get('/messages') .expect(200) .expect('content-type', 'application/json; charset=utf-8') .expect('[]', done); }); it('POST should return 204', function(done) { request .post('/messages') .send('hello') .expect(204, done); }); it('GET should return the message', function(done) { request .get('/messages') .expect(200) .expect('content-type', 'application/json; charset=utf-8') .expect('["hello"]', done); }); it('GET should return no more messages', function(done) { request .get('/messages') .expect(200) .expect('content-type', 'application/json; charset=utf-8') .expect('[]', done); }); }); ================================================ FILE: hello-world/app.js ================================================ const Koa = require('koa'); const app = module.exports = new Koa(); app.use(async function(ctx) { ctx.body = 'Hello World'; }); if (!module.parent) app.listen(3000); ================================================ FILE: hello-world/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Hello World', function() { after(function() { server.close(); }); it('should say "Hello World"', function(done) { request .get('/') .expect(200) .expect('Hello World', done); }); }); ================================================ FILE: multipart/app.js ================================================ /** * Multipart example downloading all the files to disk using co-busboy. * If all you want is to download the files to a temporary folder, * just use https://github.com/cojs/multipart instead of copying this code * as it handles file descriptor limits whereas this does not. */ const os = require('os'); const path = require('path'); const Koa = require('koa'); const fs = require('fs-promise'); const koaBody = require('koa-body'); const app = module.exports = new Koa(); app.use(koaBody({ multipart: true })); app.use(async function(ctx) { // create a temporary folder to store files const tmpdir = path.join(os.tmpdir(), uid()); // make the temporary directory await fs.mkdir(tmpdir); const filePaths = []; const files = ctx.request.files || {}; for (let key in files) { const file = files[key]; const filePath = path.join(tmpdir, file.name); const reader = fs.createReadStream(file.path); const writer = fs.createWriteStream(filePath); reader.pipe(writer); filePaths.push(filePath); } ctx.body = filePaths; }); if (!module.parent) app.listen(3000); function uid() { return Math.random().toString(36).slice(2); } ================================================ FILE: multipart/test.js ================================================ require('should'); const fs = require('fs'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); // https://github.com/mscdex/busboy/blob/master/test/test-types-multipart.js const ct = 'multipart/form-data; boundary=---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k'; const body = [ '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', 'Content-Disposition: form-data; name="file_name_0"', '', 'super alpha file', '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', 'Content-Disposition: form-data; name="file_name_1"', '', 'super beta file', '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', 'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', 'Content-Type: application/octet-stream', '', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', 'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', 'Content-Type: application/octet-stream', '', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' ].join('\r\n'); describe('Multipart Files', function() { after(function() { server.close(); }); it('should store all the files', function(done) { request .post('/') .set('Content-Type', ct) .send(body) .expect(200) .end(function(err, res) { if (err) return done(err); const files = res.body; files.should.have.length(2); fs.stat(files[0], function(err) { if (err) return done(err); fs.stat(files[1], done); }); }); }); }); ================================================ FILE: negotiation/app.js ================================================ const Koa = require('koa'); const app = module.exports = new Koa(); const tobi = { _id: '123', name: 'tobi', species: 'ferret' }; const loki = { _id: '321', name: 'loki', species: 'ferret' }; const users = { tobi: tobi, loki: loki }; // content negotiation middleware. // note that you should always check for // presence of a body, and sometimes you // may want to check the type, as it may // be a stream, buffer, string, etc. app.use(async function(ctx, next) { await next(); // no body? nothing to format, early return if (!ctx.body) return; // Check which type is best match by giving // a list of acceptable types to `req.accepts()`. const type = ctx.accepts('json', 'html', 'xml', 'text'); // not acceptable if (type === false) ctx.throw(406); // accepts json, koa handles this for us, // so just return if (type === 'json') return; // accepts xml if (type === 'xml') { ctx.type = 'xml'; ctx.body = '' + ctx.body.name + ''; return; } // accepts html if (type === 'html') { ctx.type = 'html'; ctx.body = '

' + ctx.body.name + '

'; return; } // default to text ctx.type = 'text'; ctx.body = ctx.body.name; }); // filter responses, in this case remove ._id // since it's private app.use(async function(ctx, next) { await next(); if (!ctx.body) return; delete ctx.body._id; }); // try $ GET /tobi // try $ GET /loki app.use(async function(ctx) { const name = ctx.path.slice(1); const user = users[name]; ctx.body = user; }); if (!module.parent) app.listen(3000); ================================================ FILE: negotiation/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('negotiation', function() { after(function() { server.close(); }); describe('json', function() { it('should respond with json', function(done) { request .get('/tobi') .set('Accept', 'application/json') .expect(200) .expect('Content-Type', /json/) .expect({ name: 'tobi', species: 'ferret' }, done); }); }); describe('xml', function() { it('should respond with xml', function(done) { request .get('/tobi') .set('Accept', 'application/xml') .expect(200) .expect('Content-Type', /xml/) .expect('tobi', done); }); }); describe('html', function() { it('should respond with html', function(done) { request .get('/tobi') .set('Accept', 'text/html') .expect(200) .expect('Content-Type', /html/) .expect('

tobi

', done); }); }); describe('text', function() { it('should respond with html', function(done) { request .get('/tobi') .set('Accept', 'text/plain') .expect(200) .expect('Content-Type', /plain/) .expect('tobi', done); }); }); describe('*/*', function() { it('should give precedence to the first accepted type', function(done) { request .get('/tobi') .set('Accept', '*/*') .expect(200) .expect('Content-Type', /json/) .expect('{"name":"tobi","species":"ferret"}', done); }); }); }); ================================================ FILE: package.json ================================================ { "name": "koa-examples", "description": "Examples using Koa", "version": "0.0.1", "repository": "https://github.com/koajs/examples", "bugs": "https://github.com/koajs/examples/issues", "dependencies": { "ejs": "^2.5.6", "fs-promise": "^2.0.3", "koa": "^2.2.0", "koa-basic-auth": "^2.0.0", "koa-body": "^4.0.8", "koa-compose": "^4.0.0", "koa-csrf": "^3.0.6", "koa-logger": "^3.0.0", "@koa/router": "^8.0.5", "koa-session": "^5.0.0", "koa-static": "^3.0.0", "koa-views": "^6.0.2", "streaming-json-stringify": "^3.1.0", "swig-templates": "^2.0.3" }, "devDependencies": { "eslint": "^3.8.1", "eslint-config-koa": "^2.0.2", "eslint-config-standard": "^6.2.0", "eslint-plugin-promise": "^3.3.0", "eslint-plugin-standard": "^2.0.1", "mocha": "^5.0.0", "should": "^3.3.2", "supertest": "^3.0.0" }, "scripts": { "test": "NODE_ENV=test mocha --harmony --reporter spec --require should */test.js", "lint": "eslint ." }, "engines": { "node": ">= 7.6" }, "license": "MIT" } ================================================ FILE: stream-file/README.md ================================================ # stream-file Stream a file from the local directory. To invoke, the following command begins listening on localhost:3000. node --harmony app.js To see results: http://localhost:3000/README.md or http://localhost:3000/other-file-in-the-directory ## Interesting points 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.) 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. 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(). 4. This example also uses the createReadStream() function to create a stream which is another way to handle data (asynchronously) from a file. 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. ================================================ FILE: stream-file/app.js ================================================ const Koa = require('koa'); const fs = require('fs'); const app = module.exports = new Koa(); const path = require('path'); const extname = path.extname; // try GET /app.js app.use(async function(ctx) { const fpath = path.join(__dirname, ctx.path); const fstat = await stat(fpath); if (fstat.isFile()) { ctx.type = extname(fpath); ctx.body = fs.createReadStream(fpath); } }); if (!module.parent) app.listen(3000); /** * thunkify stat */ function stat(file) { return new Promise(function(resolve, reject) { fs.stat(file, function(err, stat) { if (err) { reject(err); } else { resolve(stat); } }); }); } ================================================ FILE: stream-file/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Stream File', function() { after(function() { server.close(); }); it('GET /app.js', function(done) { request .get('/app.js') .expect('content-type', /application\/javascript/) .expect(200, done); }); it('GET /test.js', function(done) { request .get('/test.js') .expect('content-type', /application\/javascript/) .expect(200, done); }); it('GET /alksjdf.js', function(done) { request .get('/lajksdf.js') .expect(404, done); }); it('GET /', function(done) { request .get('/') .expect(404, done); }); }); ================================================ FILE: stream-objects/README.md ================================================ # stream-objects Stream a out Javascript objects. To invoke, the following command begins listening on localhost:3000. node --harmony app.js To see results: http://localhost:3000 ## Interesting points 1. In app.js, the setImmediate() function writes out a JS object { id: 1 } to the stream, then declares another setImmediate function. 2. The second setImmediate() function writes a second JS object { id: 2 } to the stream, then declares a third setImmediate() function. 3. The final setImmediate() calls stream.end() to indicate that there is no more data. 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. 4. The resulting web page shows an array containing both the JS objects, in the order they were initiated, like this: [ {"id":1} , {"id":2} ] ================================================ FILE: stream-objects/app.js ================================================ const Koa = require('koa'); const JSONStream = require('streaming-json-stringify'); const app = module.exports = new Koa(); app.use(async function(ctx) { ctx.type = 'json'; const stream = ctx.body = JSONStream(); stream.on('error', ctx.onerror); setImmediate(function() { stream.write({ id: 1 }); setImmediate(function() { stream.write({ id: 2 }); setImmediate(function() { stream.end(); }); }); }); }); if (!module.parent) app.listen(3000); ================================================ FILE: stream-objects/test.js ================================================ require('should'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Stream Objects', function() { after(function() { server.close(); }); it('GET /', function(done) { request .get('/app.js') .expect(200, function(err, res) { if (err) return done(err); res.body.should.eql([{ id: 1 }, { id: 2 }]); done(); }); }); }); ================================================ FILE: stream-server-side-events/README.md ================================================ # stream-server-side-events This program continually sends events to the web browser. It simulates a series of log file entries that have been appended to a file. To invoke, the following command begins listening on localhost:3000. node --harmony app.js To see results: http://localhost:3000 ## Interesting points 1. ================================================ FILE: stream-server-side-events/app.js ================================================ const Koa = require('koa'); const app = module.exports = new Koa(); const sse = require('./sse'); const db = require('./db'); app.use(async function(ctx) { // otherwise node will automatically close this connection in 2 minutes ctx.req.setTimeout(Number.MAX_VALUE); ctx.type = 'text/event-stream; charset=utf-8'; ctx.set('Cache-Control', 'no-cache'); ctx.set('Connection', 'keep-alive'); const body = ctx.body = sse(); const stream = db.subscribe('some event'); stream.pipe(body); // if the connection closes or errors, // we stop the SSE. const socket = ctx.socket; socket.on('error', close); socket.on('close', close); function close() { stream.unpipe(body); socket.removeListener('error', close); socket.removeListener('close', close); } }); if (!module.parent) app.listen(3000); ================================================ FILE: stream-server-side-events/db.js ================================================ var Readable = require('stream').Readable; var inherits = require('util').inherits; /** * Returns a new subscription event event. * Real APIs would care about the `event`. */ exports.subscribe = function(event, options) { return Subscription(options); }; /** * Subscription stream. Just increments the result. * Never ends! */ inherits(Subscription, Readable); function Subscription(options) { if (!(this instanceof Subscription)) return new Subscription(options); options = options || {}; Readable.call(this, options); this.value = 0; } Subscription.prototype._read = function() { while (this.push(String(this.value++))) {} }; ================================================ FILE: stream-server-side-events/sse.js ================================================ /** * Create a transform stream that converts a stream * to valid `data: \n\n' events for SSE. */ var Transform = require('stream').Transform; var inherits = require('util').inherits; module.exports = SSE; inherits(SSE, Transform); function SSE(options) { if (!(this instanceof SSE)) return new SSE(options); options = options || {}; Transform.call(this, options); } SSE.prototype._transform = function(data, enc, cb) { this.push('data: ' + data.toString('utf8') + '\n\n'); cb(); }; ================================================ FILE: stream-view/README.md ================================================ # stream-view This is a "Hello World" application, using a view that inherits from a Readable stream. To invoke, the following command begins listening on localhost:3000. node app.js To see results: http://localhost:3000 ## Interesting points 1. The main function of app.js instantiates a "View" from the view.js file. 2. The View overrides the Readable's _read() function with an empty function. 3. The View also overrides the Readable's render() function to do the following: 1. Immediately push out the text for the \ of the page 2. Yield to a function that will ultimately (in the next tick) return the "Hello World" text. The render() function pauses at that point. 3. When that function returns, render() resumes, and assigns the return value to the `body` variable 4. Push out the returned text wrapped in \ tags 5. Push out the closing \ tag and 6. Close the connection with push(null) ================================================ FILE: stream-view/app.js ================================================ const Koa = require('koa'); const View = require('./view'); const app = module.exports = new Koa(); app.use(async function(ctx) { ctx.type = 'html'; ctx.body = new View(ctx); }); if (!module.parent) app.listen(3000); ================================================ FILE: stream-view/test.js ================================================ require('should'); const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Stream View', function() { after(function() { server.close(); }); it('GET /', function(done) { request .get('/') .expect(200, function(err, res) { if (err) return done(err); res.should.be.html; res.text.should.include('Hello World'); res.text.should.include('

Hello World

'); done(); }); }); }); ================================================ FILE: stream-view/view.js ================================================ 'use strict'; const Readable = require('stream').Readable; const co = require('co'); module.exports = class View extends Readable { constructor(context) { super(); // render the view on a different loop co.call(this, this.render).catch(context.onerror); } _read() {} *render() { // push the immediately this.push('Hello World'); // render the on the next tick const body = yield done => { setImmediate(() => done(null, '

Hello World

')); }; this.push('' + body + ''); // close the document this.push(''); // end the stream this.push(null); }; }; ================================================ FILE: templates/app.js ================================================ const path = require('path'); const views = require('koa-views'); const Koa = require('koa'); const app = module.exports = new Koa(); // setup views, appending .ejs // when no extname is given to render() app.use(views(path.join(__dirname, '/views'), { extension: 'ejs' })); // dummy data const user = { name: { first: 'Tobi', last: 'Holowaychuk' }, species: 'ferret', age: 3 }; // render app.use(async function(ctx) { await ctx.render('user', { user }); }); if (!module.parent) app.listen(3000); ================================================ FILE: templates/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Templates', function() { after(function() { server.close(); }); describe('GET /', function() { it('should respond with a rendered view', function(done) { request .get('/') .expect(200) .expect('

Tobi is a 3 year old ferret.

', done); }); }); }); ================================================ FILE: templates/views/user.ejs ================================================

<%= user.name.first %> is a <%= user.age %> year old <%= user.species %>.

================================================ FILE: upload/app.js ================================================ /** * Module dependencies. */ const logger = require('koa-logger'); const serve = require('koa-static'); const koaBody = require('koa-body'); const Koa = require('koa'); const fs = require('fs'); const app = new Koa(); const os = require('os'); const path = require('path'); // log requests app.use(logger()); app.use(koaBody({ multipart: true })); // custom 404 app.use(async function(ctx, next) { await next(); if (ctx.body || !ctx.idempotent) return; ctx.redirect('/404.html'); }); // serve files from ./public app.use(serve(path.join(__dirname, '/public'))); // handle uploads app.use(async function(ctx, next) { // ignore non-POSTs if ('POST' != ctx.method) return await next(); const file = ctx.request.files.file; const reader = fs.createReadStream(file.path); const stream = fs.createWriteStream(path.join(os.tmpdir(), Math.random().toString())); reader.pipe(stream); console.log('uploading %s -> %s', file.name, stream.path); ctx.redirect('/'); }); // listen app.listen(3000); console.log('listening on port 3000'); ================================================ FILE: upload/public/404.html ================================================ Not Found

Sorry! Can't find that.

The page you requested cannot be found.

================================================ FILE: upload/public/index.html ================================================ Upload

File Upload

Try uploading multiple files at a time.

================================================ FILE: vhost/app.js ================================================ const compose = require('koa-compose'); const Koa = require('koa'); const app = module.exports = new Koa(); // virtual host apps const wwwSubdomain = composer(require('./apps/koa')); const barSubdomain = composer(require('./apps/array')); // compose koa apps and middleware arrays // to be used later in our host switch generator function composer(app) { const middleware = app instanceof Koa ? app.middleware : app; return compose(middleware); } // look ma, global response logging for all our apps! app.use(async function(ctx, next) { const start = new Date(); await next(); const ms = new Date() - start; if ('test' != process.env.NODE_ENV) { console.log('%s %s %s - %sms', ctx.host, ctx.method, ctx.url, ms); } }); // switch between appropriate hosts calling their // composed middleware with the appropriate context. app.use(async function(ctx, next) { switch (ctx.hostname) { case 'example.com': case 'www.example.com': // displays `Hello from main app` // and sets a `X-Custom` response header return await wwwSubdomain.apply(this, [ctx, next]); case 'bar.example.com': // displays `Howzit? From bar middleware bundle` // and sets a `X-Response-Time` response header return await barSubdomain.apply(this, [ctx, next]); } // everything else, eg: 127.0.0.1:3000 // will propagate to 404 Not Found return await next(); }); if (!module.parent) app.listen(3000); ================================================ FILE: vhost/apps/array.js ================================================ // rather than koa apps we can also use array // bundles of middleware to the same effect. async function responseTime(ctx, next) { const start = new Date(); await next(); const ms = new Date() - start; ctx.set('X-Response-Time', ms + 'ms'); } async function index(ctx, next) { await next(); if ('/' != ctx.url) return; ctx.body = 'Howzit? From bar middleware bundle'; } module.exports = [ responseTime, index ]; ================================================ FILE: vhost/apps/koa.js ================================================ const Koa = require('koa'); // koa app const app = new Koa(); app.use(async function(ctx, next) { await next(); ctx.set('X-Custom', 'Dub Dub Dub App'); }); app.use(async function(ctx, next) { await next(); if ('/' != ctx.url) return; ctx.body = 'Hello from www app'; }); module.exports = app; ================================================ FILE: vhost/test.js ================================================ const app = require('./app'); const server = app.listen(); const request = require('supertest').agent(server); describe('Virtual Host', function() { after(function() { server.close(); }); describe('www subdomain koa app', function() { describe('when GET /', function() { it('should say "Hello from www app"', function(done) { request .get('/') .set('Host', 'www.example.com') .expect(200) .expect('Hello from www app', done); }); it('should set X-Custom', function(done) { request .get('/') .set('Host', 'www.example.com') .expect('X-Custom', 'Dub Dub Dub App') .expect(200, done); }); }); describe('when GET / without subdomain', function() { it('should say "Hello from www app"', function(done) { request .get('/') .set('Host', 'example.com') .expect(200) .expect('Hello from www app', done); }); it('should set X-Custom', function(done) { request .get('/') .set('Host', 'example.com') .expect('X-Custom', 'Dub Dub Dub App') .expect(200, done); }); }); describe('when not GET /', function() { it('should 404', function(done) { request .get('/aklsjdf') .set('Host', 'example.com') .expect(404, done); }); }); }); describe('bar subdomain array bundle', function() { describe('when GET /', function() { it('should say "Howzit? From bar middleware bundle"', function(done) { request .get('/') .set('Host', 'bar.example.com') .expect(200) .expect('Howzit? From bar middleware bundle', done); }); it('should set X-Response-Time', function(done) { request .get('/') .set('Host', 'bar.example.com') .expect('X-Response-Time', /ms$/) .expect(200, done); }); }); describe('when not GET /', function() { it('should 404', function(done) { request .get('/aklsjdf') .set('Host', 'bar.example.com') .expect(404, done); }); }); }); describe('default vhost', function() { describe('when GET /', function() { it('should 404', function(done) { request .get('/') .set('Host', '127.0.0.1') .expect(404, done); }); }); }); });