Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanong committed Jun 29, 2014
0 parents commit c466d1d
Show file tree
Hide file tree
Showing 39 changed files with 1,119 additions and 0 deletions.
75 changes: 75 additions & 0 deletions 01-co/README.md
@@ -0,0 +1,75 @@

Koa uses [co](https://github.com/visionmedia/co) under the hood,
so to fully understand how Koa works,
you must understand co.

Co uses ES6 generators.
You can tell if a function is a generator if it has a star:

```js
function* () {

}
```

`yield` is a keyword specific to generators and allows users to __arbitrarily suspend__ functions at any `yield` point.
`yield` is not a magical async function - co does all that magic behind the scenes.

You can think of `co`'s use of generators like this with node callbacks:

```js
function* () {
var val = yield /* breakpoint */ function (next) {
// #pause at the break point

// execute an asynchronous function
setTimeout(function () {
// return the value node.js callback-style
next(null, 1);
}, 100);
}

assert(val === 1);
}
```

This workshop will not cover all the intricacies of generators as that alone would
be its own workshop, and most people (including myself) wouldn't be able
to understand generators in less than a day.

## Yieldables

You can only `yield` a few types of "async" things in Co. We call these "yieldables".:

### Thunks

Thunks are asynchronous functions that only allow a callback:

```js
function (done) {
setTimeout(function () {
done(/* error: */ null, true);
}, 100)
}
```

If there are additional arguments on this function,
a neat trick is to simply wrap it in a thunk or return a thunk.
You may be interested in [thunkify](https://github.com/visionmedia/node-thunkify),
but we will learn how to "thunkify" node.js-style callbacks in this exercise.

### Promises

We won't show you how to write code with promises,
but you can `yield` them!

## Creating yieldables

In this exercise, we will learn how to create "yieldables" from callbacks.
We will not go into depth of how generators and `co` works as it is unnecessary for Koa.

## Learning more

- [thunkify](https://github.com/visionmedia/node-thunkify)
- [mz](https://github.com/normalize/mz)
- [co-fs](https://github.com/visionmedia/co-fs)
36 changes: 36 additions & 0 deletions 01-co/index.js
@@ -0,0 +1,36 @@

var fs = require('fs');

/**
* Create a yieldable version of `fs.stat()`:
*
* app.use(function* () {
* var stats = yield exports.stat(__filename);
* })
*
* Hint: you can return a yieldable.
*/

exports.stat = function (filename) {

};

/**
* Create a yieldable version of `fs.exists()`:
*
*
* app.use(function* () {
* var exists = yield exports.exists(__filename);
* })
*
* Note that `fs.exists()` simply wraps `fs.stat()`.
* If `fs.stat()` does not return an error, then the file exists!
*
* Hint: don't use fs.exists() as it's not a proper callback.
* In other words, the first argument returned in its callback
* is not an error object, unlike node callbacks.
*/

exports.exists = function (filename) {

};
33 changes: 33 additions & 0 deletions 01-co/test.js
@@ -0,0 +1,33 @@

var co = require('co');
var assert = require('assert');

var fs = require('./index.js');

describe('.stats()', function () {
it('should stat this file', co(function* () {
var stats = yield fs.stat(__filename);
assert.ok(stats.size);
}))

it('should throw on a nonexistent file', co(function* () {
try {
yield fs.stat(__filename + 'lkjaslkdjflaksdfasdf');
// the above expression should throw,
// so this error will never be thrown
throw new Error('nope');
} catch (err) {
assert(err.message !== 'nope');
}
}))
})

describe('.exists()', function () {
it('should find this file', co(function* () {
assert.equal(true, yield fs.exists(__filename))
}))

it('should return false for a nonexistent file', co(function* () {
assert.equal(false, yield fs.exists(__filename + 'kljalskjdfklajsdf'))
}))
})
42 changes: 42 additions & 0 deletions 02-hello-world/README.md
@@ -0,0 +1,42 @@

Now that you sort of have the idea of generators,
the next step is to make the simplest Koa app, ever.

Unlike Express where you use node.js' `req` and `res` object,
Koa exposes its own very similar `this.request` and `this.response` objects.
Also unlike Express and node.js,
Koa uses getters and setters instead of methods.

For example,
in node.js, you might be used to the following:

```js
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('hello world');
```

In Express, there is a shortcut:

```js
res.send('hello world');
```

However, in Koa, we use getter/setter:

```js
app.use(function* () {
this.response.body = 'hello world';
})
```

## Exercise

Make an app that returns `hello world` for every response.
Verify the following are correct:

- Status Code: `200`
- `Content-Length`: `11`
- `Content-Type`: `text/plain; charset=utf-8`

> Hint: Koa sets these headers for you with strings!
13 changes: 13 additions & 0 deletions 02-hello-world/index.js
@@ -0,0 +1,13 @@

var koa = require('koa');

var app = module.exports = koa();

/**
* Return "hello world" as a string on every request.
* Hint: this only requires a single line of code.
*/

app.use(function* () {

});
15 changes: 15 additions & 0 deletions 02-hello-world/test.js
@@ -0,0 +1,15 @@

var request = require('supertest');

var app = require('./index.js');

describe('Hello World', function () {
it('should return hello world', function (done) {
request(app.listen())
.get('/')
.expect(200)
.expect('Content-Length', 11)
.expect('Content-Type', 'text/plain; charset=utf-8')
.end(done)
})
})
70 changes: 70 additions & 0 deletions 03-routing/README.md
@@ -0,0 +1,70 @@

Unlike Express and many other frameworks, Koa does not include a router.
Without a router, routing in Koa can be done by using `this.request.path` and `yield next`.
To check if the request matches a specific path:

```js
app.use(function* (next) {
if (this.request.path === '/') {

}
})
```

To skip this middleware:

```js
app.use(function* (next) {
if (skip) return yield next;
})
```

This is very similar to Express' `next()` call.

Combining this together,
you can route paths like this:

```js
app.use(function* (next) {
// skip the rest of the code if the route does not match
if (this.request.path !== '/') return yield next;

this.response.body = 'we are at home!';
})
```

By `return`ing early,
we don't need to bother having any nested `if` and `else` statements.
Note that we're checking whether the path __does not match__,
then early returning.

Of course, you can write this the long way:

```js
app.use(function* (next) {
if (this.request.path === '/') {
this.response.body = 'hello!';
} else {
yield next;
}
})
```

## Exercise

Create an app that returns the following responses from the following routes:

- `/` - `hello world`
- `/404` - `page not found`
- `/500` - `internal server error`.

In a real app, having each route as its own middleware is more ideal
as it allows for easier refactoring.

## Learn More

There are more properties you're probably interested in when routing:

- `this.request.method`
- `this.request.query`
- `this.request.host`
16 changes: 16 additions & 0 deletions 03-routing/index.js
@@ -0,0 +1,16 @@

var koa = require('koa');

var app = module.exports = koa();

app.use(function* () {

});

app.use(function* () {

});

app.use(function* () {

});
24 changes: 24 additions & 0 deletions 03-routing/test.js
@@ -0,0 +1,24 @@

var request = require('supertest');

var app = require('./index.js');

describe('Routing', function () {
it('GET / should return "hello world"', function (done) {
request(app.listen())
.get('/')
.expect('hello world', done);
})

it('GET /404 should return "page not found"', function (done) {
request(app.listen())
.get('/404')
.expect('page not found', done);
})

it('get /500 should return "internal server error"', function (done) {
request(app.listen())
.get('/500')
.expect('internal server error', done);
})
})
42 changes: 42 additions & 0 deletions 04-bodies/README.md
@@ -0,0 +1,42 @@

So far, we've only used strings as bodies.
Koa supports the following types of bodies:

- Strings
- Buffers
- Streams (node)
- JSON Objects

If the body is not treated as the first three,
Koa will assume that you want to send a JSON response,
treating REST APIs as first-class citizens.

```js
app.use(function* (next) {
this.response.body = {
message: 'this will be sent as a JSON response!'
};
})
```

When setting a stream as a body,
Koa will automatically add any error handlers so you don't have to worry about error handling.

```js
var fs = require('fs');

app.use(function* (next) {
this.response.body = fs.createReadStream('some_file.txt');
// koa will automatically handle errors and leaks
})
```

## Exercise

Create an app that returns a stream when the client requests `/stream` and a JSON body when the client requests `/json`.

## Bonus

When setting the body of the stream, Koa can't infer the `Content-Length` of the
body. Set the `Content-Length` header yourself using `this.response.length=`
and the "yieldable" version of `fs` you've created in exercise 1.

0 comments on commit c466d1d

Please sign in to comment.