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 |
|
24 | To prepare your microservice for running in the production environment, firstly install `micro`:
|
25 |
|
26 | ```bash
|
27 | npm install --save micro
|
28 | ```
|
29 |
|
30 | ## Usage
|
31 |
|
32 | Create 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
|
35 | module.exports = (req, res) => {
|
36 | res.end('Welcome to Micro')
|
37 | }
|
38 | ```
|
39 |
|
40 | Micro 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
|
43 | module.exports = () => 'Welcome to Micro'
|
44 | ```
|
45 |
|
46 | Next, 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 |
|
57 | Once all of that is done, the server can be started like this:
|
58 |
|
59 | ```bash
|
60 | npm start
|
61 | ```
|
62 |
|
63 | And 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 |
|
116 | Micro 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
|
119 | const sleep = require('then-sleep')
|
120 |
|
121 | module.exports = async (req, res) => {
|
122 | await sleep(500)
|
123 | return 'Ready!'
|
124 | }
|
125 | ```
|
126 |
|
127 | ### Transpilation
|
128 |
|
129 | The 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 |
|
131 | In order to do that, you firstly need to install it:
|
132 |
|
133 | ```bash
|
134 | npm install --save async-to-gen
|
135 | ```
|
136 |
|
137 | And 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 |
|
147 | Once these two steps are done, you can transpile the code by running this command:
|
148 |
|
149 | ```bash
|
150 | npm run build
|
151 | ```
|
152 |
|
153 | That'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 |
|
157 | When you want to set the port using an environment variable you can use:
|
158 |
|
159 | ```
|
160 | micro -l tcp://0.0.0.0:$PORT
|
161 | ```
|
162 |
|
163 | Optionally you can add a default if it suits your use case:
|
164 |
|
165 | ```
|
166 | micro -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 |
|
171 | Note 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 |
|
183 | For parsing the incoming request body we included an async functions `buffer`, `text` and `json`
|
184 |
|
185 | ```js
|
186 | const {buffer, text, json} = require('micro')
|
187 |
|
188 | module.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 |
|
214 | For other types of data check the [examples](#body-parsing-examples)
|
215 |
|
216 | ### Sending a different status code
|
217 |
|
218 | So 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
|
221 | const {send} = require('micro')
|
222 |
|
223 | module.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 |
|
244 | You can use Micro programmatically by requiring Micro directly:
|
245 |
|
246 | ```js
|
247 | const micro = require('micro')
|
248 | const sleep = require('then-sleep')
|
249 |
|
250 | const server = micro(async (req, res) => {
|
251 | await sleep(500)
|
252 | return 'Hello world'
|
253 | })
|
254 |
|
255 | server.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 |
|
283 | Micro allows you to write robust microservices. This is accomplished primarily by bringing sanity back to error handling and avoiding callback soup.
|
284 |
|
285 | If 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 |
|
287 | If 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
|
290 | const rateLimit = require('my-rate-limit')
|
291 |
|
292 | module.exports = async (req, res) => {
|
293 | await rateLimit(req)
|
294 | // ... your code
|
295 | }
|
296 | ```
|
297 |
|
298 | If the API endpoint is abused, it can throw an error with ``createError`` like so:
|
299 |
|
300 | ```js
|
301 | if (tooMany) {
|
302 | throw createError(429, 'Rate limit exceeded')
|
303 | }
|
304 | ```
|
305 |
|
306 | Alternatively you can create the `Error` object yourself
|
307 |
|
308 | ```js
|
309 | if (tooMany) {
|
310 | const err = new Error('Rate limit exceeded')
|
311 | err.statusCode = 429
|
312 | throw err
|
313 | }
|
314 | ```
|
315 |
|
316 | The nice thing about this model is that the `statusCode` is merely a suggestion. The user can override it:
|
317 |
|
318 | ```js
|
319 | try {
|
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 |
|
329 | If 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 |
|
331 | In order to set up your own error handling mechanism, you can use composition in your handler:
|
332 |
|
333 | ```js
|
334 | const {send} = require('micro')
|
335 |
|
336 | const 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 |
|
345 | module.exports = handleErrors(async (req, res) => {
|
346 | throw new Error('What happened here?')
|
347 | })
|
348 | ```
|
349 |
|
350 | ## Testing
|
351 |
|
352 | Micro makes tests compact and a pleasure to read and write.
|
353 | We recommend [ava](https://github.com/sindresorhus/ava), a highly parallel Micro test framework with built-in support for async tests:
|
354 |
|
355 | ```js
|
356 | const micro = require('micro')
|
357 | const test = require('ava')
|
358 | const listen = require('test-listen')
|
359 | const request = require('request-promise')
|
360 |
|
361 | test('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 |
|
376 | Look at [test-listen](https://github.com/zeit/test-listen) for a
|
377 | function that returns a URL with an ephemeral port every time it's called.
|
378 |
|
379 | ## Contributing
|
380 |
|
381 | 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
|
382 | 2. Link the package to the global module directory: `npm link`
|
383 | 3. 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 |
|
385 | As always, you can run the [AVA](https://github.com/sindresorhus/ava) and [ESLint](http://eslint.org) tests using: `npm test`
|
386 |
|
387 | ## Credits
|
388 |
|
389 | Thanks 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)
|