UNPKG

7.63 kBMarkdownView Raw
1_**Micri** — Asynchronous HTTP microservices_
2
3> micri is an archaic non-SI decimal metric prefix for 10−14. Its symbol was mc.
4
5[Wikipedia - Micri-](https://en.wikipedia.org/wiki/Micri-)
6
7[![Install Size](https://packagephobia.now.sh/badge?p=micri)](https://packagephobia.now.sh/result?p=micri)
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* **Micri**: 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
21## Usage
22
23```js
24const micri = require('micri')
25
26const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
27
28const server = micri(async (req, res) => {
29 await sleep(500)
30 return 'Hello world'
31})
32
33server.listen(3000)
34```
35
36And go to this URL: `http://localhost:3000` - 🎉
37
38### `async` & `await`
39
40<p><details>
41 <summary><b>Examples</b></summary>
42 <ul><li><a href="./examples/external-api-call">Fetch external api</a></li></ul>
43</details></p>
44
45Micri is built for usage with async/await. You can read more about async / await [here](https://zeit.co/blog/async-and-await)
46
47```js
48const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
49
50module.exports = async (req, res) => {
51 await sleep(500);
52 return 'Ready!';
53}
54```
55
56### Body parsing
57
58<p id="body-parsing-examples"><details>
59 <summary><b>Examples</b></summary>
60 <ul>
61 <li><a href="./examples/json-body-parsing">Parse JSON</a></li>
62 <li><a href="./examples/urlencoded-body-parsing">Parse urlencoded form (html `form` tag)</a></li>
63 </ul>
64</details></p>
65
66For parsing the incoming request body we included an async functions `buffer`, `text` and `json`
67
68```js
69const {buffer, text, json} = require('micri')
70
71module.exports = async (req, res) => {
72 const buf = await buffer(req)
73 console.log(buf)
74 // <Buffer 7b 22 70 72 69 63 65 22 3a 20 39 2e 39 39 7d>
75 const txt = await text(req)
76 console.log(txt)
77 // '{"price": 9.99}'
78 const js = await json(req)
79 console.log(js.price)
80 // 9.99
81 return ''
82}
83```
84
85### API
86
87##### `buffer(req, { limit = '1mb', encoding = 'utf8' })`
88##### `text(req, { limit = '1mb', encoding = 'utf8' })`
89##### `json(req, { limit = '1mb', encoding = 'utf8' })`
90
91- Buffers and parses the incoming body and returns it.
92- Exposes an `async` function that can be run with `await`.
93- Can be called multiple times, as it caches the raw request body the first time.
94- `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'`.
95- If JSON parsing fails, an `Error` is thrown with `statusCode` set to `400` (see [Error Handling](#error-handling))
96
97For other types of data check the [examples](#body-parsing-examples)
98
99### Sending a different status code
100
101So far we have used `return` to send data to the client. `return 'Hello World'` is the equivalent of `send(res, 200, 'Hello World')`.
102
103```js
104const {send} = require('micri')
105
106module.exports = async (req, res) => {
107 const statusCode = 400
108 const data = { error: 'Custom error message' }
109
110 send(res, statusCode, data)
111}
112```
113
114##### `send(res, statusCode, data = null)`
115
116- Use `require('micri').send`.
117- `statusCode` is a `Number` with the HTTP status code, and must always be supplied.
118- 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.
119 - `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).
120 - `Buffer`: `data` is written as an `octet-stream`.
121 - `object`: `data` is serialized as JSON.
122 - `string`: `data` is written as-is.
123- If JSON serialization fails (for example, if a cyclical reference is found), a `400` error is thrown. See [Error Handling](#error-handling).
124
125##### micri(fn)
126
127- This function is exposed as the `default` export.
128- Use `require('micri')`.
129- 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.
130- The supplied function is run with `await`. So it can be `async`
131
132##### sendError(req, res, error)
133
134- Use `require('micri').sendError`.
135- Used as the default handler for errors thrown.
136- Automatically sets the status code of the response based on `error.statusCode`.
137- Sends the `error.message` as the body.
138- Stacks are printed out with `console.error` and during development (when `NODE_ENV` is set to `'development'`) also sent in responses.
139- Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`.
140
141## Error Handling
142
143Micri allows you to write robust microservices. This is accomplished primarily
144by bringing sanity back to error handling and avoiding callback soup.
145
146If an error is thrown and not caught by you, the response will automatically be
147`500`. **Important:** Error stacks will be printed as `console.error` and during
148development mode (if the env variable `NODE_ENV` is `'development'`), they will
149also be included in the responses.
150
151If the error object throw is an instance of `MicriError` the `message`,
152`statusCode` and `code` properties of the object are used for the HTTP response.
153
154Let's say you want to write a rate limiting module:
155
156```js
157const rateLimit = require('my-rate-limit')
158
159module.exports = async (req, res) => {
160 await rateLimit(req)
161 // ... your code
162}
163```
164
165If the API endpoint is abused, it can throw an error with ``createError`` like so:
166
167```js
168if (tooMany) {
169 throw MicriError(429, 'rate_limited' 'Rate limit exceeded')
170}
171```
172
173The nice thing about this model is that the `statusCode` is merely a suggestion.
174The user can override it:
175
176```js
177try {
178 await rateLimit(req)
179} catch (err) {
180 if (429 == err.statusCode) {
181 // perhaps send 500 instead?
182 send(res, 500)
183 }
184}
185```
186
187If the error is based on another error that **Micri** caught, like a `JSON.parse`
188exception, then `originalError` will point to it. If a generic error is caught,
189the status will be set to `500`.
190
191In order to set up your own error handling mechanism, you can use composition in
192your handler:
193
194```js
195const {send} = require('micri')
196
197const handleErrors = fn => async (req, res) => {
198 try {
199 return await fn(req, res)
200 } catch (err) {
201 console.log(err.stack)
202 send(res, 500, 'My custom error!')
203 }
204}
205
206module.exports = handleErrors(async (req, res) => {
207 throw new Error('What happened here?')
208})
209```
210
211## Contributing
212
2131. [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
2142. Link the package to the global module directory: `npm link`
2153. Within the module you want to test your local development instance of Micri, just link it to the dependencies: `npm link micri`. Instead of the default one from npm, node will now use your clone of Micri!
216
217As always, you can run the [AVA](https://github.com/sindresorhus/ava) and [ESLint](http://eslint.org) tests using: `npm test`