UNPKG

13.4 kBMarkdownView Raw
1<img src="https://raw.githubusercontent.com/zeit/art/6451bc300e00312d970527274f316f9b2c07a27e/micro/logo.png" width="50"/>
2
3_**Micro** — Asynchronous HTTP microservices_
4
5[![CircleCI](https://circleci.com/gh/zeit/micro/tree/master.svg?style=shield)](https://circleci.com/gh/zeit/micro/tree/master)
6[![Install Size](https://packagephobia.now.sh/badge?p=micro)](https://packagephobia.now.sh/result?p=micro)
7[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit)
8
9## Features
10
11* **Easy**: Designed for usage with `async` and `await` ([more](https://zeit.co/blog/async-and-await))
12* **Fast**: Ultra-high performance (even JSON parsing is opt-in)
13* **Micro**: The whole project is ~260 lines of code
14* **Agile**: Super easy deployment and containerization
15* **Simple**: Oriented for single purpose modules (function)
16* **Standard**: Just HTTP!
17* **Explicit**: No middleware - modules declare all [dependencies](https://github.com/amio/awesome-micro)
18* **Lightweight**: With all dependencies, the package weighs less than a megabyte
19
20## Installation
21
22**Important:** Micro is only meant to be used in production. In development, you should use [micro-dev](https://github.com/zeit/micro-dev), which provides you with a tool belt specifically tailored for developing microservices.
23
24To prepare your microservice for running in the production environment, firstly install `micro`:
25
26```bash
27npm install --save micro
28```
29
30## Usage
31
32Create an `index.js` file and export a function that accepts the standard [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) objects:
33
34```js
35module.exports = (req, res) => {
36 res.end('Welcome to Micro')
37}
38```
39
40Micro provides [useful helpers](https://github.com/zeit/micro#body-parsing) but also handles return values – so you can write it even shorter!
41
42```js
43module.exports = () => 'Welcome to Micro'
44```
45
46Next, ensure that the `main` property inside `package.json` points to your microservice (which is inside `index.js` in this example case) and add a `start` script:
47
48```json
49{
50 "main": "index.js",
51 "scripts": {
52 "start": "micro"
53 }
54}
55```
56
57Once all of that is done, the server can be started like this:
58
59```bash
60npm start
61```
62
63And go to this URL: `http://localhost:3000` - 🎉
64
65### Command line
66
67```
68 micro - Asynchronous HTTP microservices
69
70 USAGE
71
72 $ micro --help
73 $ micro --version
74 $ micro [-l listen_uri [-l ...]] [entry_point.js]
75
76 By default micro will listen on 0.0.0.0:3000 and will look first
77 for the "main" property in package.json and subsequently for index.js
78 as the default entry_point.
79
80 Specifying a single --listen argument will overwrite the default, not supplement it.
81
82 OPTIONS
83
84 --help shows this help message
85
86 -v, --version displays the current version of micro
87
88 -l, --listen listen_uri specify a URI endpoint on which to listen (see below) -
89 more than one may be specified to listen in multiple places
90
91 ENDPOINTS
92
93 Listen endpoints (specified by the --listen or -l options above) instruct micro
94 to listen on one or more interfaces/ports, UNIX domain sockets, or Windows named pipes.
95
96 For TCP (traditional host/port) endpoints:
97
98 $ micro -l tcp://hostname:1234
99
100 For UNIX domain socket endpoints:
101
102 $ micro -l unix:/path/to/socket.sock
103
104 For Windows named pipe endpoints:
105
106 $ micro -l pipe:\\.\pipe\PipeName
107```
108
109### `async` & `await`
110
111<p><details>
112 <summary><b>Examples</b></summary>
113 <ul><li><a href="./examples/external-api-call">Fetch external api</a></li></ul>
114</details></p>
115
116Micro is built for usage with async/await. You can read more about async / await [here](https://zeit.co/blog/async-and-await)
117
118```js
119const sleep = require('then-sleep')
120
121module.exports = async (req, res) => {
122 await sleep(500)
123 return 'Ready!'
124}
125```
126
127### Transpilation
128
129The package takes advantage of native support for `async` and `await`, which is available as of **Node.js 8.0.0**! In turn, we suggest either using at least this version both in development and production (if possible), or transpiling the code using [async-to-gen](https://github.com/leebyron/async-to-gen), if you can't use the latest Node.js version.
130
131In order to do that, you firstly need to install it:
132
133```bash
134npm install --save async-to-gen
135```
136
137And then add the transpilation command to the `scripts.build` property inside `package.json`:
138
139```json
140{
141 "scripts": {
142 "build": "async-to-gen input.js > output.js"
143 }
144}
145```
146
147Once these two steps are done, you can transpile the code by running this command:
148
149```bash
150npm run build
151```
152
153That's all it takes to transpile by yourself. But just to be clear: **Only do this if you can't use Node.js 8.0.0**! If you can, `async` and `await` will just work right out of the box.
154
155### Port Based on Environment Variable
156
157When you want to set the port using an environment variable you can use:
158
159```
160micro -l tcp://0.0.0.0:$PORT
161```
162
163Optionally you can add a default if it suits your use case:
164
165```
166micro -l tcp://0.0.0.0:${PORT-3000}
167```
168
169`${PORT-3000}` will allow a fallback to port `3000` when `$PORT` is not defined.
170
171Note that this only works in Bash.
172
173### Body parsing
174
175<p id="body-parsing-examples"><details>
176 <summary><b>Examples</b></summary>
177 <ul>
178 <li><a href="./examples/json-body-parsing">Parse JSON</a></li>
179 <li><a href="./examples/urlencoded-body-parsing">Parse urlencoded form (html `form` tag)</a></li>
180 </ul>
181</details></p>
182
183For parsing the incoming request body we included an async functions `buffer`, `text` and `json`
184
185```js
186const {buffer, text, json} = require('micro')
187
188module.exports = async (req, res) => {
189 const buf = await buffer(req)
190 console.log(buf)
191 // <Buffer 7b 22 70 72 69 63 65 22 3a 20 39 2e 39 39 7d>
192 const txt = await text(req)
193 console.log(txt)
194 // '{"price": 9.99}'
195 const js = await json(req)
196 console.log(js.price)
197 // 9.99
198 return ''
199}
200```
201
202### API
203
204##### `buffer(req, { limit = '1mb', encoding = 'utf8' })`
205##### `text(req, { limit = '1mb', encoding = 'utf8' })`
206##### `json(req, { limit = '1mb', encoding = 'utf8' })`
207
208- Buffers and parses the incoming body and returns it.
209- Exposes an `async` function that can be run with `await`.
210- Can be called multiple times, as it caches the raw request body the first time.
211- `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'`.
212- If JSON parsing fails, an `Error` is thrown with `statusCode` set to `400` (see [Error Handling](#error-handling))
213
214For other types of data check the [examples](#body-parsing-examples)
215
216### Sending a different status code
217
218So far we have used `return` to send data to the client. `return 'Hello World'` is the equivalent of `send(res, 200, 'Hello World')`.
219
220```js
221const {send} = require('micro')
222
223module.exports = async (req, res) => {
224 const statusCode = 400
225 const data = { error: 'Custom error message' }
226
227 send(res, statusCode, data)
228}
229```
230
231##### `send(res, statusCode, data = null)`
232
233- Use `require('micro').send`.
234- `statusCode` is a `Number` with the HTTP status code, and must always be supplied.
235- If `data` is supplied it is sent in the response. Different input types are processed appropriately, and `Content-Type` and `Content-Length` are automatically set.
236 - `Stream`: `data` is piped as an `octet-stream`. Note: it is _your_ responsibility to handle the `error` event in this case (usually, simply logging the error and aborting the response is enough).
237 - `Buffer`: `data` is written as an `octet-stream`.
238 - `object`: `data` is serialized as JSON.
239 - `string`: `data` is written as-is.
240- If JSON serialization fails (for example, if a cyclical reference is found), a `400` error is thrown. See [Error Handling](#error-handling).
241
242### Programmatic use
243
244You can use Micro programmatically by requiring Micro directly:
245
246```js
247const micro = require('micro')
248const sleep = require('then-sleep')
249
250const server = micro(async (req, res) => {
251 await sleep(500)
252 return 'Hello world'
253})
254
255server.listen(3000)
256```
257
258##### micro(fn)
259
260- This function is exposed as the `default` export.
261- Use `require('micro')`.
262- 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.
263- The supplied function is run with `await`. So it can be `async`
264
265##### sendError(req, res, error)
266
267- Use `require('micro').sendError`.
268- Used as the default handler for errors thrown.
269- Automatically sets the status code of the response based on `error.statusCode`.
270- Sends the `error.message` as the body.
271- Stacks are printed out with `console.error` and during development (when `NODE_ENV` is set to `'development'`) also sent in responses.
272- Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`.
273
274##### createError(code, msg, orig)
275
276- Use `require('micro').createError`.
277- Creates an error object with a `statusCode`.
278- Useful for easily throwing errors with HTTP status codes, which are interpreted by the [built-in error handling](#error-handling).
279- `orig` sets `error.originalError` which identifies the original error (if any).
280
281## Error Handling
282
283Micro allows you to write robust microservices. This is accomplished primarily by bringing sanity back to error handling and avoiding callback soup.
284
285If an error is thrown and not caught by you, the response will automatically be `500`. **Important:** Error stacks will be printed as `console.error` and during development mode (if the env variable `NODE_ENV` is `'development'`), they will also be included in the responses.
286
287If the `Error` object that's thrown contains a `statusCode` property, that's used as the HTTP code to be sent. Let's say you want to write a rate limiting module:
288
289```js
290const rateLimit = require('my-rate-limit')
291
292module.exports = async (req, res) => {
293 await rateLimit(req)
294 // ... your code
295}
296```
297
298If the API endpoint is abused, it can throw an error with ``createError`` like so:
299
300```js
301if (tooMany) {
302 throw createError(429, 'Rate limit exceeded')
303}
304```
305
306Alternatively you can create the `Error` object yourself
307
308```js
309if (tooMany) {
310 const err = new Error('Rate limit exceeded')
311 err.statusCode = 429
312 throw err
313}
314```
315
316The nice thing about this model is that the `statusCode` is merely a suggestion. The user can override it:
317
318```js
319try {
320 await rateLimit(req)
321} catch (err) {
322 if (429 == err.statusCode) {
323 // perhaps send 500 instead?
324 send(res, 500)
325 }
326}
327```
328
329If the error is based on another error that **Micro** caught, like a `JSON.parse` exception, then `originalError` will point to it. If a generic error is caught, the status will be set to `500`.
330
331In order to set up your own error handling mechanism, you can use composition in your handler:
332
333```js
334const {send} = require('micro')
335
336const handleErrors = fn => async (req, res) => {
337 try {
338 return await fn(req, res)
339 } catch (err) {
340 console.log(err.stack)
341 send(res, 500, 'My custom error!')
342 }
343}
344
345module.exports = handleErrors(async (req, res) => {
346 throw new Error('What happened here?')
347})
348```
349
350## Testing
351
352Micro makes tests compact and a pleasure to read and write.
353We recommend [ava](https://github.com/sindresorhus/ava), a highly parallel Micro test framework with built-in support for async tests:
354
355```js
356const micro = require('micro')
357const test = require('ava')
358const listen = require('test-listen')
359const request = require('request-promise')
360
361test('my endpoint', async t => {
362 const service = micro(async (req, res) => {
363 micro.send(res, 200, {
364 test: 'woot'
365 })
366 })
367
368 const url = await listen(service)
369 const body = await request(url)
370
371 t.deepEqual(JSON.parse(body).test, 'woot')
372 service.close()
373})
374```
375
376Look at [test-listen](https://github.com/zeit/test-listen) for a
377function that returns a URL with an ephemeral port every time it's called.
378
379## Contributing
380
3811. [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
3822. Link the package to the global module directory: `npm link`
3833. Within the module you want to test your local development instance of Micro, just link it to the dependencies: `npm link micro`. Instead of the default one from npm, node will now use your clone of Micro!
384
385As always, you can run the [AVA](https://github.com/sindresorhus/ava) and [ESLint](http://eslint.org) tests using: `npm test`
386
387## Credits
388
389Thanks to Tom Yandell and Richard Hodgson for donating the name "micro" on [npm](https://www.npmjs.com)!
390
391## Authors
392
393- Guillermo Rauch ([@rauchg](https://twitter.com/rauchg)) - [ZEIT](https://zeit.co)
394- Leo Lamprecht ([@notquiteleo](https://twitter.com/notquiteleo)) - [ZEIT](https://zeit.co)
395- Tim Neutkens ([@timneutkens](https://twitter.com/timneutkens)) - [ZEIT](https://zeit.co)