Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

micro

Package Overview
Dependencies
Maintainers
2
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

micro - npm Package Compare versions

Comparing version
6.2.1
to
7.0.0
+69
bin/micro.js
#!/usr/bin/env node
// Native
const {resolve} = require('path')
// Packages
const asyncToGen = require('async-to-gen/register')
const updateNotifier = require('update-notifier')
const nodeVersion = require('node-version')
const args = require('args')
const isAsyncSupported = require('is-async-supported')
// Ours
const pkg = require('../package')
// Throw an error if node version is too low
if (nodeVersion.major < 6) {
console.error(`Error! Micro requires at least version 6 of Node. Please upgrade!`)
process.exit(1)
}
// Let user know if there's an update
// This isn't important when deployed to Now
if (!process.env.NOW && pkg.dist) {
updateNotifier({pkg}).notify()
}
args
.option('port', 'Port to listen on', process.env.PORT || 3000)
.option(['H', 'host'], 'Host to listen on', '0.0.0.0')
const flags = args.parse(process.argv)
let file = args.sub[0]
if (!file) {
try {
const packageJson = require(resolve(process.cwd(), 'package.json'))
file = packageJson.main || 'index.js'
} catch (err) {
if ('MODULE_NOT_FOUND' !== err.code) {
console.error(`micro: Could not read \`package.json\`: ${err.message}`)
process.exit(1)
}
}
}
if (!file) {
console.error('micro: Please supply a file.')
args.showHelp()
}
if ('/' !== file[0]) {
file = resolve(process.cwd(), file)
}
if (!isAsyncSupported()) {
// Support for keywords "async" and "await"
const pathSep = process.platform === 'win32' ? '\\\\' : '/'
asyncToGen({
includes: new RegExp(`.*micro?${pathSep}(lib|bin)|${file}.*`),
excludes: null,
sourceMaps: false
})
}
// Load package core with async/await support
// If needed... Otherwise use the native implementation
require('../lib/index')(file, flags)
// Packages
const detect = require('detect-port')
// Ours
const serve = require('./server')
const listening = require('./listening')
const getMod = file => {
let mod
try {
mod = require(file)
if (mod && 'object' === typeof mod) {
mod = mod.default
}
} catch (err) {
console.error(`micro: Error when importing ${file}: ${err.stack}`)
process.exit(1)
}
if ('function' !== typeof mod) {
console.error(`micro: "${file}" does not export a function.`)
process.exit(1)
}
return mod
}
module.exports = (file, flags) => {
const server = serve(getMod(file))
let port = flags.port
let host = flags.host
detect(port).then(open => {
let inUse = open !== port
if (inUse) {
port = open
inUse = {
old: flags.port,
open
}
}
if (host === '0.0.0.0') {
host = null
}
server.listen(port, host, async err => {
if (err) {
console.error('micro:', err.stack)
process.exit(1)
}
return await listening(server, inUse)
})
})
}
// Packages
const {copy} = require('copy-paste')
const ip = require('ip')
const chalk = require('chalk')
const copyToClipboard = async text => {
try {
await copy(text)
return true
} catch (err) {
return false
}
}
module.exports = async (server, inUse) => {
const details = server.address()
const ipAddress = ip.address()
const url = `http://${ipAddress}:${details.port}`
process.on('SIGINT', () => {
server.close()
process.exit(0)
})
if (!process.env.NOW) {
let message = chalk.green('Micro is running!')
if (inUse) {
message += ' ' + chalk.red(`(on port ${inUse.open},` +
` because ${inUse.old} is already in use)`)
}
message += '\n\n'
const localURL = `http://localhost:${details.port}`
message += `• ${chalk.bold('Local: ')} ${localURL}\n`
message += `• ${chalk.bold('On Your Network: ')} ${url}\n\n`
const copied = await copyToClipboard(localURL)
if (copied) {
message += `${chalk.grey('Copied local address to clipboard!')}\n\n`
}
process.stdout.write('\x1Bc')
process.stdout.write(message)
}
}
// Native
const {resolve} = require('path')
// Packages
const isAsyncSupported = require('is-async-supported')
const asyncToGen = require('async-to-gen/register')
// Support for keywords "async" and "await"
if (!isAsyncSupported()) {
const path = resolve(__dirname, './server')
asyncToGen({
includes: new RegExp(`.*${path}.*`),
excludes: null,
sourceMaps: false
})
}
module.exports = require('./server')
// Native
const server = require('http').Server
// Packages
const getRawBody = require('raw-body')
const typer = require('media-typer')
const isStream = require('isstream')
const DEV = 'development' === process.env.NODE_ENV
const TESTING = 'test' === process.env.NODE_ENV
const serve = fn => server((req, res) => {
run(req, res, fn, sendError)
})
module.exports = exports = serve
exports.run = run
exports.json = json
exports.send = send
exports.sendError = sendError
exports.createError = createError
async function run(req, res, fn, onError) {
try {
const val = await fn(req, res)
// Return 204 No Content if value is null
if (null === val) {
send(res, 204, null)
}
// Return a undefined-null value -> send
if (undefined !== val) {
send(res, res.statusCode || 200, val)
}
} catch (err) {
await onError(req, res, err)
}
}
// maps requests to buffered raw bodies so that
// multiple calls to `json` work as expected
const rawBodyMap = new WeakMap()
async function json(req, {limit = '1mb'} = {}) {
try {
const type = req.headers['content-type']
const length = req.headers['content-length']
const encoding = typer.parse(type).parameters.charset
let str = rawBodyMap.get(req)
if (!str) {
str = await getRawBody(req, {limit, length, encoding})
rawBodyMap.set(req, str)
}
try {
return JSON.parse(str)
} catch (err) {
throw createError(400, 'Invalid JSON', err)
}
} catch (err) {
if ('entity.too.large' === err.type) {
throw createError(413, `Body exceeded ${limit} limit`, err)
} else {
throw createError(400, 'Invalid body', err)
}
}
}
function send(res, code, obj = null) {
res.statusCode = code
if (null !== obj) {
if (Buffer.isBuffer(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream')
}
res.setHeader('Content-Length', obj.length)
res.end(obj)
} else if (isStream(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream')
}
obj.pipe(res)
} else {
let str
if ('object' === typeof obj) {
// we stringify before setting the header
// in case `JSON.stringify` throws and a
// 500 has to be sent instead
// the `JSON.stringify` call is split into
// two cases as `JSON.stringify` is optimized
// in V8 if called with only one argument
if (DEV) {
str = JSON.stringify(obj, null, 2)
} else {
str = JSON.stringify(obj)
}
res.setHeader('Content-Type', 'application/json')
} else {
str = obj
}
res.setHeader('Content-Length', Buffer.byteLength(str))
res.end(str)
}
} else {
res.end()
}
}
function sendError(req, res, {statusCode, message, stack}) {
if (statusCode) {
send(res, statusCode, DEV ? stack : message)
} else {
send(res, 500, DEV ? stack : 'Internal Server Error')
}
if (!TESTING) {
console.error(stack)
}
}
function createError(code, msg, orig) {
const err = new Error(msg)
err.statusCode = code
err.originalError = orig
return err
}
+29
-25
{
"name": "micro",
"version": "6.2.1",
"version": "7.0.0",
"description": "Asynchronous HTTP microservices",
"main": "./dist/index.js",
"main": "./lib/load.js",
"files": [
"dist",
"bin"
"bin",
"lib"
],
"greenkeeper": {
"emails": false
},
"scripts": {
"prepublish": "npm run build",
"pretest": "npm run build",
"build": "mkdir -p dist && async-to-gen lib/index.js > dist/index.js",
"test": "xo && ava"
"precommit": "npm run lint",
"lint": "xo",
"test": "npm run lint && ava"
},

@@ -22,5 +18,2 @@ "ava": {

"async-to-gen/register"
],
"files": [
"test/index.js"
]

@@ -32,2 +25,5 @@ },

"semicolon": false,
"ignores": [
"examples/**/*"
],
"rules": {

@@ -40,8 +36,9 @@ "max-lines": 0,

"yoda": 0,
"no-negated-condition": 0
"no-negated-condition": 0,
"import/no-dynamic-require": 0,
"unicorn/no-process-exit": 0
}
},
"bin": {
"micro": "./bin/micro",
"micro-serve": "./bin/micro"
"micro": "./bin/micro.js"
},

@@ -66,6 +63,7 @@ "repository": "zeit/micro",

"devDependencies": {
"ava": "^0.17.0",
"ava": "^0.18.1",
"husky": "^0.13.1",
"request": "^2.74.0",
"request-promise": "^4.1.1",
"resumer": "0.0.0",
"resumer": "^0.0.0",
"then-sleep": "^1.0.1",

@@ -75,9 +73,15 @@ "xo": "^0.17.0"

"dependencies": {
"async-to-gen": "1.3.0",
"is-async-supported": "1.2.0",
"isstream": "0.1.2",
"media-typer": "0.3.0",
"minimist": "1.2.0",
"raw-body": "2.2.0"
"args": "^2.2.4",
"async-to-gen": "^1.3.2",
"chalk": "^1.1.3",
"copy-paste": "^1.3.0",
"detect-port": "^1.1.0",
"ip": "^1.1.4",
"is-async-supported": "^1.2.0",
"isstream": "^0.1.2",
"media-typer": "^0.3.0",
"node-version": "^1.0.0",
"raw-body": "^2.2.0",
"update-notifier": "^1.0.3"
}
}
+174
-142

@@ -1,2 +0,2 @@

![](https://cldup.com/JDmmHX3uhF.svg)
![](https://raw.githubusercontent.com/zeit/art/31913be3107827adf10e1f491ec61480f63e19af/micro/logo.png)

@@ -18,79 +18,91 @@ _**Micro —** Async ES6 HTTP microservices_

* **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
## Usage
The following example `sleep.js` will wait before responding (without blocking!)
Firstly, install it:
```js
const {send} = require('micro')
const sleep = require('then-sleep')
```bash
npm install --save micro
```
module.exports = async function (req, res) {
await sleep(500)
send(res, 200, 'Ready!')
Then add a `start` script to your `package.json` like this:
```json
{
"main": "index.js",
"scripts": {
"start": "micro"
}
}
```
To run the microservice on port `3000`, use the `micro` command:
After that, we have to create an `index.js` file and populate it:
```bash
micro sleep.js
```js
module.exports = (req, res) => 'Welcome to micro'
```
To run the microservice on port `3000` and localhost instead of listening on every interface, use the `micro` command:
Once all of that is done, just start the server:
```bash
micro -H localhost sleep.js
npm start
```
## Usage
And go to this URL: `http://localhost:3000` - 🎉
Install the package (requires at least Node v6):
### `async` & `await`
```js
npm install --save micro
```
<p><details>
<summary><b>Examples</b></summary>
<ul><li><a href="./examples/external-api-call">Fetch external api</a></li></ul>
</details></p>
And start using it in your `package.json` file:
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 (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.
### API
`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.
#### micro
**`micro(fn)`**
### Body parsing
- 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:
<p id="body-parsing-examples"><details>
<summary><b>Examples</b></summary>
<ul>
<li><a href="./examples/json-body-parsing">Parse JSON</a></li>
<li><a href="./examples/urlencoded-body-parsing">Parse urlencoded form (html `form` tag)</a></li>
</ul>
</details></p>
```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);
```
For parsing the incoming request body we included an async function `json`
#### json
```js
const {json} = require('micro')
module.exports = async (req, res) => {
const data = await json(req)
console.log(data.price)
return ''
}
```
#### API
**`json(req, { limit = '1mb' })`**

@@ -104,15 +116,22 @@

- 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);
}
```
For other types of data check the [examples](#body-parsing-examples)
#### send
### 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 (req, res) => {
const statusCode = 400
const data = { error: 'Custom error message' }
send(res, statusCode, data)
}
```
#### API
**`send(res, statusCode, data = null)`**

@@ -128,59 +147,28 @@

- 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' });
}
```
### Programmatic use
#### return
You can use micro programmatically by requiring micro directly:
**`return val;`**
```js
const micro = require('micro')
const sleep = require('then-sleep')
- Returning `val` from your function is shorthand for: `send(res, 200, val)`.
- Example
const server = micro(async (req, res) => {
await sleep(500)
return 'Hello world'
})
```js
module.exports = function (req, res) {
return {message: 'Hello!'};
}
```
server.listen(3000)
```
- Returning a promise works as well!
- Example
#### API
```js
const sleep = require('then-sleep')
module.exports = async function (req, res) {
return new Promise(async (resolve) => {
await sleep(100);
resolve('I Promised');
});
}
```
**`micro(fn)`**
#### sendError
- 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`
**`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.
- During development (when `NODE_ENV` is set to `'development'`), stacks are printed out with `console.error` and 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
**`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).
<a name="error-handling"></a>
### Error handling

@@ -190,3 +178,3 @@

If an error is thrown and not caught by you, the response will automatically be `500`. **Important:** during development mode (if the env variable `NODE_ENV` is `'development'`), error stacks will be printed as `console.error` and included in the responses.
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.

@@ -197,23 +185,24 @@ 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:

const rateLimit = require('my-rate-limit')
module.exports = async function (req, res) {
await rateLimit(req);
// … your code
module.exports = async (req, res) => {
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
}

@@ -226,7 +215,7 @@ ```

try {
await rateLimit(req);
await rateLimit(req)
} catch (err) {
if (429 == err.statusCode) {
// perhaps send 500 instead?
send(res, 500);
send(res, 500)
}

@@ -243,18 +232,36 @@ }

```js
module.exports = handleErrors(async (req, res) => {
throw new Error('What happened here?');
});
const {send} = require('micro')
function handleErrors (fn) {
return async function (req, res) {
try {
return await fn(req, res);
} catch (err) {
console.log(err.stack);
send(res, 500, 'My custom error!');
}
const handleErrors = fn => async (req, res) => {
try {
return await fn(req, res)
} catch (err) {
console.log(err.stack)
send(res, 500, 'My custom error!')
}
}
module.exports = handleErrors(async (req, res) => {
throw new Error('What happened here?')
})
```
#### 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

@@ -266,19 +273,22 @@

```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 service = micro(async (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.

@@ -298,2 +308,8 @@

To use native `async/await` on Node v7.x, run `micro` like the following.
```bash
node --harmony-async-await node_modules/.bin/micro .
```
### Deployment

@@ -311,3 +327,3 @@

"scripts": {
"start": "micro -p 3000"
"start": "micro"
}

@@ -319,2 +335,18 @@ }

#### 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

@@ -321,0 +353,0 @@

Sorry, the diff of this file is not supported yet

// Native
const server = require('http').Server
// Packages
const getRawBody = require('raw-body')
const typer = require('media-typer')
const isStream = require('isstream')
const DEV = 'development' === process.env.NODE_ENV
const TESTING = 'test' === process.env.NODE_ENV
module.exports = exports = serve
exports.run = run
exports.json = json
exports.send = send
exports.sendError = sendError
exports.createError = createError
function serve(fn, {onError = null} = {}) {
if (onError) {
console.warn('[DEPRECATED] onError is deprecated and will be removed in a future release. Please use your own try/catch as needed.')
}
return server((req, res) => {
run(req, res, fn, onError || sendError)
})
}
function run(req, res, fn, onError) {return __async(function*(){
try {
const val = yield fn(req, res)
// return a non-null value -> send
if (null !== val && undefined !== val) {
send(res, res.statusCode || 200, val)
}
} catch (err) {
yield onError(req, res, err)
}
}())}
// maps requests to buffered raw bodies so that
// multiple calls to `json` work as expected
const rawBodyMap = new WeakMap()
function json(req, {limit = '1mb'} = {}) {return __async(function*(){
try {
const type = req.headers['content-type']
const length = req.headers['content-length']
const encoding = typer.parse(type).parameters.charset
let str = rawBodyMap.get(req)
if (!str) {
str = yield getRawBody(req, {limit, length, encoding})
rawBodyMap.set(req, str)
}
try {
return JSON.parse(str)
} catch (err) {
throw createError(400, 'Invalid JSON', err)
}
} catch (err) {
if ('entity.too.large' === err.type) {
throw createError(413, `Body exceeded ${limit} limit`, err)
} else {
throw createError(400, 'Invalid body', err)
}
}
}())}
function send(res, code, obj = null) {
res.statusCode = code
if (null !== obj) {
if (Buffer.isBuffer(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream')
}
res.setHeader('Content-Length', obj.length)
res.end(obj)
} else if (isStream(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream')
}
obj.pipe(res)
} else {
let str
if ('object' === typeof obj) {
// we stringify before setting the header
// in case `JSON.stringify` throws and a
// 500 has to be sent instead
// the `JSON.stringify` call is split into
// two cases as `JSON.stringify` is optimized
// in V8 if called with only one argument
if (DEV) {
str = JSON.stringify(obj, null, 2)
} else {
str = JSON.stringify(obj)
}
res.setHeader('Content-Type', 'application/json')
} else {
str = obj
}
res.setHeader('Content-Length', Buffer.byteLength(str))
res.end(str)
}
} else {
res.end()
}
}
function sendError(req, res, {statusCode, message, stack}) {
if (statusCode) {
send(res, statusCode, DEV ? stack : message)
} else {
send(res, 500, DEV ? stack : 'Internal Server Error')
}
if (!TESTING) {
console.error(stack)
}
}
function createError(code, msg, orig) {
const err = new Error(msg)
err.statusCode = code
err.originalError = orig
return err
}
function __async(g){return new Promise(function(s,j){function c(a,x){try{var r=g[x?"throw":"next"](a)}catch(e){j(e);return}r.done?s(r.value):Promise.resolve(r.value).then(c,d)}function d(e){c(e,1)}c()})}