diff --git a/README.md b/README.md index 560cb4b..6e2664e 100644 --- a/README.md +++ b/README.md @@ -15,82 +15,98 @@ _**Micro —** Async ES6 HTTP microservices_ * **Simple**. Oriented for single purpose modules (function). * **Explicit**. No middleware. Modules declare all dependencies. * **Standard**. Just HTTP! -* **Lightweight**. The package is small and the `async` transpilation fast and transparent +* **Lightweight**. The package is small and the `async` transpilation is fast and transparent -## Example - -The following example `sleep.js` will wait before responding (without blocking!) +## Usage -```js -const {send} = require('micro') -const sleep = require('then-sleep') +Install it: -module.exports = async function (req, res) { - await sleep(500) - send(res, 200, 'Ready!') -} +``` +npm install --save micro ``` -To run the microservice on port `3000`, use the `micro` command: +Add a script to your `package.json` like this: ```bash micro sleep.js ``` -To run the microservice on port `3000` and localhost instead of listening on every interface, use the `micro` command: - -```bash -micro -H localhost sleep.js +```json +{ + "main": "index.js", + "scripts": { + "start": "micro -p 3000" + } +} ``` -## Usage +After that, we have to create an `index.js` file -Install the package (requires at least Node v6): +Populate `./index.js` inside your project: ```js -npm install --save micro +module.exports = function (req, res) { + return 'Welcome to micro' +} ``` -And start using it in your `package.json` file: +and then just run `npm run start` and go to http://localhost:3000 + +So far, we have written a web server that sends 'Welcome to micro' + +### Async / Await + +

+ Examples + +

+ +Micro is built for usage with async/await. You can read more about async / await [here](https://zeit.co/blog/async-and-await) ```js -"main": "index.js", -"scripts": { - "start": "micro" +const sleep = require('then-sleep') + +module.exports = async function (req, res) { + await sleep(500) + return 'Ready!' } ``` -Then write your `index.js` (see above for an example). +#### Transpilation -After that, you can make the server run by executing the following command: +We use [is-async-supported](https://github.com/timneutkens/is-async-supported) combined with [async-to-gen](https://github.com/leebyron/async-to-gen), +so that the we only convert `async` and `await` to generators when needed. -```bash -npm start -``` +If you want to do it manually, you can! `micro(1)` is idempotent and +should not interfere. + +`micro` exclusively supports Node 6+ to avoid a big transpilation +pipeline. `async-to-gen` is fast and can be distributed with +the main `micro` package due to its small size. -### API +### Body parsing -#### micro -**`micro(fn)`** +

+ Examples + +

-- This function is exposed as the `default` export. -- Use `require('micro')`. -- Returns a [`http.Server`](https://nodejs.org/dist/latest-v4.x/docs/api/http.html#http_class_http_server) that uses the provided `fn` as the request handler. -- The supplied function is run with `await`. It can be `async`! -- Example: - - ```js - const micro = require('micro'); - const sleep = require('then-sleep'); - const srv = micro(async function (req, res) { - await sleep(500); - res.writeHead(200); - res.end('woot'); - }); - srv.listen(3000); - ``` - -#### json +For parsing the incoming request body we included an async function `json` + +```js +const { json } = require('micro') + +module.exports = async function (req, res) { + const data = await json(req) + console.log(data.price) + return '' +} +``` + +#### API **`json(req, { limit = '1mb' })`** @@ -100,18 +116,23 @@ npm start - Can be called multiple times, as it caches the raw request body the first time. - `limit` is how much data is aggregated before parsing at max. Otherwise, an `Error` is thrown with `statusCode` set to `413` (see [Error Handling](#error-handling)). It can be a `Number` of bytes or [a string](https://www.npmjs.com/package/bytes) like `'1mb'`. - If JSON parsing fails, an `Error` is thrown with `statusCode` set to `400` (see [Error Handling](#error-handling)) -- Example: - - ```js - const { json, send } = require('micro'); - module.exports = async function (req, res) { - const data = await json(req); - console.log(data.price); - send(res, 200); - } - ``` -#### send +For other types of data check the [examples](#body-parsing-examples) + +### Sending a different status code + +So far we have used `return` to send data to the client. `return 'Hello World'` is the equivalent of `send(res, 200, 'Hello World')`. + +```js +const { send } = require('micro') +module.exports = async function (req, res) { + const statusCode = 400 + const data = { error: 'Custom error message' } + send(res, statusCode, data) +} +``` + +#### API **`send(res, statusCode, data = null)`** @@ -123,62 +144,31 @@ npm start - `object`: `data` is serialized as JSON. - `string`: `data` is written as-is. - If JSON serialization fails (for example, if a cyclical reference is found), a `400` error is thrown. See [Error Handling](#error-handling). -- Example - - ```js - const { send } = require('micro') - module.exports = async function (req, res) { - send(res, 400, { error: 'Please use a valid email' }); - } - ``` - -#### return -**`return val;`** - -- Returning `val` from your function is shorthand for: `send(res, 200, val)`. -- Example - - ```js - module.exports = function (req, res) { - return {message: 'Hello!'}; - } - ``` - -- Returning a promise works as well! -- Example - - ```js - const sleep = require('then-sleep') - module.exports = async function (req, res) { - return new Promise(async (resolve) => { - await sleep(100); - resolve('I Promised'); - }); - } - ``` +### Programmatic use -#### sendError +You can use micro programmatically by requiring micro directly: -**`sendError(req, res, error)`** +```js +const micro = require('micro') +const sleep = require('then-sleep') -- Use `require('micro').sendError`. -- Used as the default handler for errors thrown. -- Automatically sets the status code of the response based on `error.statusCode`. -- Sends the `error.message` as the body. -- Stacks are printed out with `console.error` and during development (when `NODE_ENV` is set to `'development'`) also sent in responses. -- Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`. +const server = micro(async function (req, res) { + await sleep(500) + return 'Hello world' +}) -#### createError +server.listen(3000) +``` -**`createError(code, msg, orig)`** +#### API -- Use `require('micro').createError`. -- Creates an error object with a `statusCode`. -- Useful for easily throwing errors with HTTP status codes, which are interpreted by the [built-in error handling](#error-handling). -- `orig` sets `error.originalError` which identifies the original error (if any). +**`micro(fn)`** - +- This function is exposed as the `default` export. +- Use `require('micro')`. +- Returns a [`http.Server`](https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_class_http_server) that uses the provided `function` as the request handler. +- The supplied function is run with `await`. So it can be `async` ### Error handling @@ -191,26 +181,26 @@ If the `Error` object that's thrown contains a `statusCode` property, that's use ```js const rateLimit = require('my-rate-limit') module.exports = async function (req, res) { - await rateLimit(req); + await rateLimit(req) // … your code } ``` -If the API endpoint is abused, it can throw an error like so: +If the API endpoint is abused, it can throw an error with ``createError`` like so: ```js if (tooMany) { - const err = new Error('Rate limit exceeded'); - err.statusCode = 429; - throw err; + throw createError(429, 'Rate limit exceeded') } ``` -Alternatively you can use ``createError`` as described above. +Alternatively you can create the `Error` object yourself ```js if (tooMany) { - throw createError(429, 'Rate limit exceeded') + const err = new Error('Rate limit exceeded') + err.statusCode = 429 + throw err } ``` @@ -218,11 +208,11 @@ The nice thing about this model is that the `statusCode` is merely a suggestion. ```js try { - await rateLimit(req); + await rateLimit(req) } catch (err) { if (429 == err.statusCode) { // perhaps send 500 instead? - send(res, 500); + send(res, 500) } } ``` @@ -234,45 +224,64 @@ If a generic error is caught, the status will be set to `500`. In order to set up your own error handling mechanism, you can use composition in your handler: ```js +const { send } = require('micro') module.exports = handleErrors(async (req, res) => { - throw new Error('What happened here?'); -}); + throw new Error('What happened here?') +}) function handleErrors (fn) { return async function (req, res) { try { - return await fn(req, res); + return await fn(req, res) } catch (err) { - console.log(err.stack); - send(res, 500, 'My custom error!'); + console.log(err.stack) + send(res, 500, 'My custom error!') } } } ``` +#### API + +**`sendError(req, res, error)`** + +- Use `require('micro').sendError`. +- Used as the default handler for errors thrown. +- Automatically sets the status code of the response based on `error.statusCode`. +- Sends the `error.message` as the body. +- Stacks are printed out with `console.error` and during development (when `NODE_ENV` is set to `'development'`) also sent in responses. +- Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`. + +**`createError(code, msg, orig)`** + +- Use `require('micro').createError`. +- Creates an error object with a `statusCode`. +- Useful for easily throwing errors with HTTP status codes, which are interpreted by the [built-in error handling](#error-handling). +- `orig` sets `error.originalError` which identifies the original error (if any). + ### Testing Micro makes tests compact and a pleasure to read and write. We recommend [ava](https://github.com/sindresorhus/ava), a highly parallel micro test framework with built-in support for async tests: ```js -const micro = require('micro'); -const test = require('ava'); -const listen = require('test-listen'); -const request = require('request-promise'); +const micro = require('micro') +const test = require('ava') +const listen = require('test-listen') +const request = require('request-promise') test('my endpoint', async t => { const service = micro(async function (req, res) { micro.send(res, 200, { test: 'woot' }) - }); + }) - const url = await listen(service); - const body = await request(url); - t.deepEqual(JSON.parse(body).test, 'woot'); -}); + const url = await listen(service) + const body = await request(url) + t.deepEqual(JSON.parse(body).test, 'woot') +}) ``` -Look at the [test-listen](https://github.com/zeit/test-listen) for a +Look at [test-listen](https://github.com/zeit/test-listen) for a function that returns a URL with an ephemeral port every time it's called. ### Transpilation @@ -312,6 +321,22 @@ You can use the `micro` CLI for `npm start`: Then simply run `npm start`! +#### Port based on environment variable + +When you want to set the port using an environment variable you can use: + +``` +micro -p $PORT +``` + +Optionally you can add a default if it suits your use case: + +``` +micro -p ${PORT:-3000} +``` + +`${PORT:-3000}` will allow a fallback to port `3000` when `$PORT` is not defined + ## Contribute 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device diff --git a/examples/external-api-call/README.md b/examples/external-api-call/README.md new file mode 100644 index 0000000..ad89423 --- /dev/null +++ b/examples/external-api-call/README.md @@ -0,0 +1,28 @@ + +# External API call example + +## How to use + +Download the example [or clone the repo](https://github.com/zeit/micro): + +```bash +curl https://codeload.github.com/zeit/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/external-api-call +cd external-api-call +``` + +Install it and run: + +```bash +npm install +npm run start +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + +## The idea behind the example + +Shows how to get data from an external api using async/await. diff --git a/examples/external-api-call/index.js b/examples/external-api-call/index.js new file mode 100644 index 0000000..d61b3cf --- /dev/null +++ b/examples/external-api-call/index.js @@ -0,0 +1,8 @@ +const fetch = require('node-fetch') + +module.exports = async function (req, res) { + const response = await fetch('https://api.example.com') + const json = await response.json() + + return json.example +} diff --git a/examples/external-api-call/package.json b/examples/external-api-call/package.json new file mode 100644 index 0000000..a7d99c3 --- /dev/null +++ b/examples/external-api-call/package.json @@ -0,0 +1,14 @@ +{ + "name": "external-api-call", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "micro" + }, + "dependencies": { + "micro": "latest", + "node-fetch": "latest" + }, + "author": "", + "license": "MIT" +} diff --git a/examples/json-body-parsing/README.md b/examples/json-body-parsing/README.md new file mode 100644 index 0000000..3bed216 --- /dev/null +++ b/examples/json-body-parsing/README.md @@ -0,0 +1,28 @@ + +# Parse JSON body example + +## How to use + +Download the example [or clone the repo](https://github.com/zeit/micro): + +```bash +curl https://codeload.github.com/zeit/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/json-body-parsing +cd json-body-parsing +``` + +Install it and run: + +```bash +npm install +npm run start +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + +## The idea behind the example + +Shows how to get data posted to your microservice using async/await. diff --git a/examples/json-body-parsing/index.js b/examples/json-body-parsing/index.js new file mode 100644 index 0000000..169fe1e --- /dev/null +++ b/examples/json-body-parsing/index.js @@ -0,0 +1,8 @@ +const {json} = require('micro') + +module.exports = async function (req, res) { + const data = await json(req) + console.log(data) + + return 'Data logged to your console' +} diff --git a/examples/json-body-parsing/package.json b/examples/json-body-parsing/package.json new file mode 100644 index 0000000..697cfdf --- /dev/null +++ b/examples/json-body-parsing/package.json @@ -0,0 +1,13 @@ +{ + "name": "json-body-parsing", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "micro" + }, + "dependencies": { + "micro": "latest" + }, + "author": "", + "license": "MIT" +} diff --git a/examples/urlencoded-body-parsing/README.md b/examples/urlencoded-body-parsing/README.md new file mode 100644 index 0000000..3bdbd01 --- /dev/null +++ b/examples/urlencoded-body-parsing/README.md @@ -0,0 +1,28 @@ + +# Parse JSON body example + +## How to use + +Download the example [or clone the repo](https://github.com/zeit/micro): + +```bash +curl https://codeload.github.com/zeit/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/urlencoded-body-parsing +cd urlencoded-body-parsing +``` + +Install it and run: + +```bash +npm install +npm run start +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + +## The idea behind the example + +Shows how to get urlencoded (html form post) data posted to your microservice using async/await. diff --git a/examples/urlencoded-body-parsing/index.js b/examples/urlencoded-body-parsing/index.js new file mode 100644 index 0000000..89027fb --- /dev/null +++ b/examples/urlencoded-body-parsing/index.js @@ -0,0 +1,8 @@ +const parse = require('urlencoded-body-parser') + +module.exports = async function (req, res) { + const data = await parse(req) + console.log(data) + + return 'Data logged to your console' +} diff --git a/examples/urlencoded-body-parsing/package.json b/examples/urlencoded-body-parsing/package.json new file mode 100644 index 0000000..62df131 --- /dev/null +++ b/examples/urlencoded-body-parsing/package.json @@ -0,0 +1,14 @@ +{ + "name": "urlencoded-body-parsing", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "micro" + }, + "dependencies": { + "micro": "latest", + "urlencoded-body-parser": "latest" + }, + "author": "", + "license": "MIT" +} diff --git a/package.json b/package.json index e58d24d..9be7eb2 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "esnext": true, "space": true, "semicolon": false, + "ignores": [ + "examples/**/*" + ], "rules": { "max-lines": 0, "ava/no-ignored-test-files": 0,