UNPKG

9.16 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[![npm version](https://badge.fury.io/js/micri.svg)](https://badge.fury.io/js/micri)
8[![Install Size](https://packagephobia.now.sh/badge?p=micri)](https://packagephobia.now.sh/result?p=micri)
9
10## Features
11
12* **Easy**: Designed for usage with `async` and `await` ([more](https://zeit.co/blog/async-and-await))
13* **Fast**: Ultra-high performance (even JSON parsing is opt-in)
14* **Micri**: The whole project is ~300 lines of code
15* **Agile**: Super easy deployment and containerization
16* **Simple**: Oriented for single purpose modules (function)
17* **Standard**: Just HTTP!
18* **Explicit**: No middleware - modules declare all [dependencies](https://github.com/amio/awesome-micro)
19* **Lightweight**: [![Install Size](https://packagephobia.now.sh/badge?p=micri)](https://packagephobia.now.sh/result?p=micri)
20
21
22## Usage
23
24```js
25const micri = require('micri')
26
27const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
28
29const server = micri(async (req, res) => {
30 await sleep(500)
31 return 'Hello world'
32})
33
34server.listen(3000)
35```
36
37And go to this URL: `http://localhost:3000` - 🎉
38
39### `async` & `await`
40
41<p><details>
42 <summary><b>Examples</b></summary>
43 <ul><li><a href="./examples/external-api-call">Fetch external api</a></li></ul>
44</details></p>
45
46Micri is built for usage with async/await. You can read more about async / await [here](https://zeit.co/blog/async-and-await)
47
48```js
49const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
50
51module.exports = async (req, res) => {
52 await sleep(500);
53 return 'Ready!';
54}
55```
56
57### Body parsing
58
59<p id="body-parsing-examples"><details>
60 <summary><b>Examples</b></summary>
61 <ul>
62 <li><a href="./examples/json-body-parsing">Parse JSON</a></li>
63 <li><a href="./examples/urlencoded-body-parsing">Parse urlencoded form (html `form` tag)</a></li>
64 </ul>
65</details></p>
66
67For parsing the incoming request body we included an async functions `buffer`, `text` and `json`
68
69```js
70const {buffer, text, json} = require('micri')
71
72module.exports = async (req, res) => {
73 const buf = await buffer(req)
74 console.log(buf)
75 // <Buffer 7b 22 70 72 69 63 65 22 3a 20 39 2e 39 39 7d>
76 const txt = await text(req)
77 console.log(txt)
78 // '{"price": 9.99}'
79 const js = await json(req)
80 console.log(js.price)
81 // 9.99
82 return ''
83}
84```
85
86### Routing
87
88Micri has a simple built-in function router. The idea is fairly simple, you can
89use it as a wrapper virtually anywhere where it will be called with
90`(req, res, optionalArgs)` and can return a promise as a response to `micri()`.
91
92Firstly you create a router by calling the `router(...)` function. The router
93function takes routes as arguments. Routes are created by calling functions
94under `on` map, and the functions are organized there by HTTP method name. These
95functions in turn take two arguments, a predicate and request handler functions.
96
97A predicate function gets the usual arguments `(req, res, opts?)`. A predicate
98function may return a truthy value if the handler function should take care of
99this request, or it may return a falsy value if the handler should not take
100this request.
101
102The order of the route arguments marks the priority order of the routes.
103Therefore if two routes would match to a request the one that was passed earlier
104in the arguments list to the `router()` function will handle the request.
105
106`otherwise()` is a special route function that will always match and thus can be
107used as the last route rule for sending an error and avoid throwing an exception
108in case no other route predicate matches.
109
110```js
111const { Router: { router } } = require('micri');
112
113micri(router(
114 on.get((req) => req.url === '/', (req, _res) => ({ message: 'Hello world!'})),
115 on.post((req) => req.url === '/', (req) => text(req)),
116 otherwise((req, res) => send(res, 400, 'Method Not Accepted'))))
117 .listen(3000);
118```
119
120## API
121
122##### `buffer(req, { limit = '1mb', encoding = 'utf8' })`
123##### `text(req, { limit = '1mb', encoding = 'utf8' })`
124##### `json(req, { limit = '1mb', encoding = 'utf8' })`
125
126- Buffers and parses the incoming body and returns it.
127- Exposes an `async` function that can be run with `await`.
128- Can be called multiple times, as it caches the raw request body the first time.
129- `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'`.
130- If JSON parsing fails, an `Error` is thrown with `statusCode` set to `400` (see [Error Handling](#error-handling))
131
132For other types of data check the [examples](#body-parsing-examples)
133
134### Sending a different status code
135
136So far we have used `return` to send data to the client. `return 'Hello World'` is the equivalent of `send(res, 200, 'Hello World')`.
137
138```js
139const {send} = require('micri')
140
141module.exports = async (req, res) => {
142 const statusCode = 400
143 const data = { error: 'Custom error message' }
144
145 send(res, statusCode, data)
146}
147```
148
149##### `send(res, statusCode, data = null)`
150
151- Use `require('micri').send`.
152- `statusCode` is a `Number` with the HTTP status code, and must always be supplied.
153- 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.
154 - `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).
155 - `Buffer`: `data` is written as an `octet-stream`.
156 - `object`: `data` is serialized as JSON.
157 - `string`: `data` is written as-is.
158- If JSON serialization fails (for example, if a cyclical reference is found), a `400` error is thrown. See [Error Handling](#error-handling).
159
160##### micri(fn)
161
162- This function is exposed as the `default` export.
163- Use `require('micri')`.
164- 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.
165- The supplied function is run with `await`. So it can be `async`
166
167##### sendError(req, res, error)
168
169- Use `require('micri').sendError`.
170- Used as the default handler for errors thrown.
171- Automatically sets the status code of the response based on `error.statusCode`.
172- Sends the `error.message` as the body.
173- Stacks are printed out with `console.error` and during development (when `NODE_ENV` is set to `'development'`) also sent in responses.
174- Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`.
175
176## Error Handling
177
178Micri allows you to write robust microservices. This is accomplished primarily
179by bringing sanity back to error handling and avoiding callback soup.
180
181If an error is thrown and not caught by you, the response will automatically be
182`500`. **Important:** Error stacks will be printed as `console.error` and during
183development mode (if the env variable `NODE_ENV` is `'development'`), they will
184also be included in the responses.
185
186If the error object throw is an instance of `MicriError` the `message`,
187`statusCode` and `code` properties of the object are used for the HTTP response.
188
189Let's say you want to write a rate limiting module:
190
191```js
192const rateLimit = require('my-rate-limit')
193
194micri((req, res) => {
195 await rateLimit(req);
196 // ... your code
197}).listen(3000);
198```
199
200If the API endpoint is abused, it can throw a `MicriError` like so:
201
202```js
203if (tooMany) {
204 throw MicriError(429, 'rate_limited' 'Rate limit exceeded');
205}
206```
207
208The nice thing about this model is that the `statusCode` is merely a suggestion.
209The user can override it:
210
211```js
212try {
213 await rateLimit(req)
214} catch (err) {
215 if (429 == err.statusCode) {
216 // perhaps send 500 instead?
217 send(res, 500);
218 }
219}
220```
221
222If the error is based on another error that **Micri** caught, like a `JSON.parse`
223exception, then `originalError` will point to it. If a generic error is caught,
224the status will be set to `500`.
225
226In order to set up your own error handling mechanism, you can use composition in
227your handler:
228
229```js
230const {send} = require('micri');
231
232const handleErrors = fn => async (req, res) => {
233 try {
234 return await fn(req, res)
235 } catch (err) {
236 console.log(err.stack)
237 send(res, 500, 'My custom error!')
238 }
239}
240
241micri(handleErrors(async (req, res) => {
242 throw new Error('What happened here?')
243})).listen(3000);
244```
245
246## Contributing
247
2481. [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
2492. Link the package to the global module directory: `npm link`
2503. 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!