diff --git a/negotiation/app.js b/negotiation/app.js
new file mode 100644
index 0000000..54879a1
--- /dev/null
+++ b/negotiation/app.js
@@ -0,0 +1,82 @@
+
+var koa = require('koa');
+var app = module.exports = koa();
+
+var tobi = {
+ _id: '123',
+ name: 'tobi',
+ species: 'ferret'
+};
+
+var loki = {
+ _id: '321',
+ name: 'loki',
+ species: 'ferret'
+};
+
+var 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(function *(next){
+ yield next;
+
+ // responses vary on accepted type
+ this.vary('Accept');
+ this.status = 'bad request';
+
+ // no body? nothing to format, early return
+ if (!this.body) return;
+
+ // accepts json, koa handles this for us,
+ // so just return
+ if (this.accepts('json')) return;
+
+ // accepts xml
+ if (this.accepts('xml')) {
+ this.type = 'xml';
+ this.body = '' + this.body.name + '';
+ return;
+ }
+
+ // accepts html
+ if (this.accepts('html')) {
+ this.type = 'html';
+ this.body = '
' + this.body.name + '
';
+ return;
+ }
+
+ // default to text
+ this.type = 'text';
+ this.body = this.body.name;
+});
+
+// filter responses, in this case remove ._id
+// since it's private
+
+app.use(function *(next){
+ yield next;
+
+ if (!this.body) return;
+
+ delete this.body._id;
+});
+
+// try $ GET /tobi
+// try $ GET /loki
+
+app.use(function *(){
+ var name = this.path.slice(1);
+ var user = users[name];
+ this.body = user;
+});
+
+if (!module.parent) app.listen(3000);
+
diff --git a/negotiation/test.js b/negotiation/test.js
new file mode 100644
index 0000000..62fea8f
--- /dev/null
+++ b/negotiation/test.js
@@ -0,0 +1,49 @@
+var app = require('./app');
+var request = require('supertest').agent(app.listen());
+
+describe('negotiation', function() {
+ describe('json', function() {
+ it('should respond with json', function(done) {
+ request
+ .get('/tobi')
+ .set('Accept', 'application/json')
+ .expect(400)
+ .expect('vary', 'Accept')
+ .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(400)
+ .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(400)
+ .expect('Content-Type', /html/)
+ .expect('tobi
', done);
+ });
+ });
+
+ describe('text', function() {
+ it('should respond with html', function(done) {
+ request
+ .get('/tobi')
+ .set('Accept', 'text/plaino')
+ .expect(400)
+ .expect('Content-Type', /plain/)
+ .expect('tobi', done);
+ });
+ });
+});