├── .eslintrc ├── .gitignore ├── .npmrc ├── .travis.yml ├── Readme.md ├── examples ├── array.js └── object.js ├── index.js ├── package.json └── test ├── .eslintrc ├── app.js └── index.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "koa" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | coverage 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - 8 3 | language: node_js 4 | script: "npm run test-travis" 5 | after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" 6 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # koa-json-filter 2 | 3 | Middleware allowing the client to filter the response to only what they need, 4 | reducing the amount of traffic over the wire using the `?filter=foo,bar,baz` querystring parameter. 5 | 6 | JSONSelect would also be great for this but I find it's a little too complicated for the average use-case, 7 | so this is just a simple key filter. 8 | 9 | ## Installation 10 | 11 | ``` 12 | $ npm install @koa/json-filter 13 | ``` 14 | 15 | Please note that if you're using an earlier version of koa 2 with function generator you need to install the older version `0.0.1` 16 | 17 | ``` 18 | $ npm install koa-json-filter@0.0.1 19 | ``` 20 | 21 | ## Options 22 | 23 | * `name` querystring param defaulting to "filter" 24 | 25 | ## Filtering customization 26 | 27 | You may also set `ctx.filter` to an array of names to filter on, 28 | for example by using a header field `X-Filter: name,email`. 29 | 30 | ## Example 31 | 32 | ### Object responses 33 | 34 | Script: 35 | 36 | ```js 37 | const Koa = require('koa'); 38 | const filter = require('@koa/json-filter'); 39 | 40 | const app = new Koa(); 41 | 42 | app.use(filter()); 43 | 44 | app.use(async ctx => { 45 | ctx.body = { 46 | name: 'tobi', 47 | email: 'tobi@segment.io', 48 | packages: 5, 49 | friends: ['abby', 'loki', 'jane'] 50 | }; 51 | }); 52 | 53 | app.listen(3000); 54 | console.log('app listening on port 3000'); 55 | ``` 56 | 57 | Response: 58 | 59 | ``` 60 | $ GET /?filter=name 61 | { 62 | "name": "tobi" 63 | } 64 | ``` 65 | 66 | ### Array responses 67 | 68 | Script: 69 | 70 | ```js 71 | const Koa = require('koa'); 72 | const filter = require('@koa/json-filter'); 73 | 74 | const app = new Koa(); 75 | 76 | app.use(filter()); 77 | 78 | app.use(async ctx => { 79 | ctx.body = [ 80 | { 81 | name: 'tobi', 82 | email: 'tobi@segment.io', 83 | packages: 5, 84 | friends: ['abby', 'loki', 'jane'] 85 | }, 86 | { 87 | name: 'loki', 88 | email: 'loki@segment.io', 89 | packages: 2, 90 | friends: ['loki', 'jane'] 91 | }, 92 | { 93 | name: 'jane', 94 | email: 'jane@segment.io', 95 | packages: 2, 96 | friends: [] 97 | }, 98 | { 99 | name: 'ewald', 100 | email: 'ewald@segment.io', 101 | packages: 2, 102 | friends: ['tobi'] 103 | } 104 | ]; 105 | }); 106 | 107 | app.listen(3000); 108 | console.log('app listening on port 3000'); 109 | ``` 110 | 111 | Response: 112 | 113 | ``` 114 | $ GET /?filter=name,email 115 | [ 116 | { 117 | "name": "tobi", 118 | "email": "tobi@segment.io" 119 | }, 120 | { 121 | "name": "loki", 122 | "email": "loki@segment.io" 123 | }, 124 | { 125 | "name": "jane", 126 | "email": "jane@segment.io" 127 | }, 128 | { 129 | "name": "ewald", 130 | "email": "ewald@segment.io" 131 | } 132 | ] 133 | ``` 134 | 135 | # License 136 | 137 | MIT 138 | -------------------------------------------------------------------------------- /examples/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | const Koa = require('koa'); 6 | const filter = require('..'); 7 | 8 | const app = new Koa(); 9 | 10 | app.use(filter()); 11 | 12 | app.use(async ctx => { 13 | ctx.body = [ 14 | { 15 | name: 'tobi', 16 | email: 'tobi@segment.io', 17 | packages: 5, 18 | friends: ['abby', 'loki', 'jane'] 19 | }, 20 | { 21 | name: 'loki', 22 | email: 'loki@segment.io', 23 | packages: 2, 24 | friends: ['loki', 'jane'] 25 | }, 26 | { 27 | name: 'jane', 28 | email: 'jane@segment.io', 29 | packages: 2, 30 | friends: [] 31 | }, 32 | { 33 | name: 'ewald', 34 | email: 'ewald@segment.io', 35 | packages: 2, 36 | friends: ['tobi'] 37 | } 38 | ]; 39 | }); 40 | 41 | app.listen(3000); 42 | console.log('app listening on port 3000'); 43 | -------------------------------------------------------------------------------- /examples/object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | const Koa = require('koa'); 6 | const filter = require('..'); 7 | 8 | const app = new Koa(); 9 | 10 | app.use(filter()); 11 | 12 | app.use(async ctx => { 13 | ctx.body = { 14 | name: 'tobi', 15 | email: 'tobi@segment.io', 16 | packages: 5, 17 | friends: ['abby', 'loki', 'jane'] 18 | }; 19 | }); 20 | 21 | app.listen(3000); 22 | console.log('app listening on port 3000'); 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter on responses. 3 | * 4 | * - `name` querystring param name [filter] 5 | * 6 | * @param {Object} opts 7 | * @return {Promise} 8 | * @api public 9 | */ 10 | 11 | module.exports = opts => { 12 | const options = opts || {}; 13 | const name = options.name || 'filter'; 14 | 15 | return async function filter(ctx, next) { 16 | await next(); 17 | 18 | const body = ctx.body; 19 | 20 | // non-json 21 | if (!body || 'object' != typeof body) return; 22 | 23 | // check for filter 24 | let filter = ctx.query[name] || ctx.filter; 25 | if (!filter) return; 26 | 27 | // split 28 | if ('string' == typeof filter) filter = filter.split(/ *, */); 29 | 30 | // filter array 31 | if (Array.isArray(body)) { 32 | ctx.body = body.map(obj => { 33 | return filter.reduce((ret, key) => { 34 | ret[key] = obj[key]; 35 | return ret; 36 | }, {}); 37 | }); 38 | 39 | return; 40 | } 41 | 42 | // filter object 43 | ctx.body = filter.reduce((ret, key) => { 44 | ret[key] = body[key]; 45 | return ret; 46 | }, {}); 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@koa/json-filter", 3 | "version": "1.0.0", 4 | "repository": "koajs/json-filter", 5 | "description": "Middleware allowing the client to filter the response to only what they need", 6 | "keywords": [ 7 | "koa", 8 | "middleware", 9 | "performance" 10 | ], 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "eslint": "^4.19.1", 14 | "eslint-config-koa": "^2.0.2", 15 | "eslint-config-standard": "^11.0.0", 16 | "eslint-plugin-import": "^2.10.0", 17 | "eslint-plugin-node": "^6.0.1", 18 | "eslint-plugin-promise": "^3.7.0", 19 | "eslint-plugin-standard": "^3.0.1", 20 | "istanbul": "^0.4.5", 21 | "koa": "^2.5.0", 22 | "koa-router": "^7.4.0", 23 | "mocha": "^5.0.5", 24 | "supertest": "^3.0.0" 25 | }, 26 | "engines": { 27 | "node": ">= 7.6.0" 28 | }, 29 | "license": "MIT", 30 | "scripts": { 31 | "lint": "eslint index.js test examples --fix", 32 | "test": "mocha", 33 | "test-cov": "istanbul cover _mocha", 34 | "test-travis": "istanbul cover _mocha --report lcovonly" 35 | }, 36 | "files": [ 37 | "index.js" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const Router = require('koa-router'); 3 | const filter = require('..'); 4 | 5 | const app = new Koa(); 6 | const router = new Router(); 7 | 8 | app.use(filter()); 9 | 10 | router.get('/', async ctx => { 11 | ctx.body = { 12 | name: 'tobi', 13 | email: 'tobi@segment.io', 14 | packages: 5, 15 | friends: ['tobi', 'loki', 'jane'] 16 | }; 17 | }); 18 | 19 | router.get('/array', async ctx => { 20 | ctx.body = [ 21 | { 22 | name: 'tobi', 23 | email: 'tobi@segment.io', 24 | packages: 5, 25 | friends: ['abby', 'loki', 'jane'] 26 | }, 27 | { 28 | name: 'loki', 29 | email: 'loki@segment.io', 30 | packages: 2, 31 | friends: ['loki', 'jane'] 32 | } 33 | ]; 34 | }); 35 | 36 | app.use(router.routes()); 37 | 38 | module.exports = app; 39 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const app = require('./app'); 3 | 4 | describe('filter()', () => { 5 | let server; 6 | 7 | beforeEach(() => (server = app.listen())); 8 | afterEach(() => server.close()); 9 | 10 | describe('when ?filter is missing', () => { 11 | it('should be ignored', async() => { 12 | await request(server) 13 | .get('/') 14 | .expect(200); 15 | }); 16 | }); 17 | 18 | describe('when ?filter is present', () => { 19 | describe('with one property', () => { 20 | it('should filter that property', async() => { 21 | await request(server) 22 | .get('/?filter=name') 23 | .expect({ name: 'tobi' }); 24 | }); 25 | }); 26 | 27 | describe('with an array response', () => { 28 | it('should filter each document', async() => { 29 | await request(server) 30 | .get('/array?filter=name') 31 | .expect([{ name: 'tobi' }, { name: 'loki' }]); 32 | }); 33 | }); 34 | 35 | describe('with multiple properties', () => { 36 | it('should split on commas', async() => { 37 | request(server) 38 | .get('/?filter=name,packages') 39 | .expect({ name: 'tobi', packages: 5 }); 40 | }); 41 | }); 42 | }); 43 | }); 44 | --------------------------------------------------------------------------------