Repository: vercel/micro
Branch: main
Commit: e7b105985170
Files: 55
Total size: 60.6 KB
Directory structure:
gitextract_h06eczrt/
├── .github/
│ └── workflows/
│ └── ci.yaml
├── .gitignore
├── .npmrc
├── .yarnrc
├── LICENSE
├── errors/
│ ├── invalid-entry.md
│ ├── invalid-package-json.md
│ ├── invalid-port-socket.md
│ ├── invalid-server-port.md
│ ├── invalid-socket.md
│ ├── no-export.md
│ ├── path-missing.md
│ └── path-not-existent.md
├── examples/
│ ├── external-api-call/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── json-body-parsing/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── socket.io-chat-app/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── package.json
│ │ └── websocket-server.js
│ ├── urlencoded-body-parsing/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── with-graphql-request/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ └── with-https/
│ ├── README.md
│ ├── index.js
│ └── package.json
├── lerna.json
├── package.json
├── packages/
│ └── micro/
│ ├── .eslintrc.js
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── bin/
│ │ │ └── micro.ts
│ │ └── lib/
│ │ ├── error.ts
│ │ ├── handler.ts
│ │ ├── index.ts
│ │ └── parse-endpoint.ts
│ ├── tsconfig.json
│ └── types/
│ └── src/
│ ├── bin/
│ │ └── micro.d.ts
│ └── lib/
│ ├── error.d.ts
│ ├── handler.d.ts
│ ├── index.d.ts
│ └── parse-endpoint.d.ts
└── test/
├── .eslintrc.js
├── package.json
├── suite/
│ ├── handler.ts
│ ├── index.ts
│ └── parse-endpoint.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI
on:
- push
- pull_request
jobs:
test:
name: Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- 16
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn run test
================================================
FILE: .gitignore
================================================
# dependencies
node_modules
# logs
npm-debug.log
yarn-error.log
# coverage
coverage
.nyc_output
# build
dist/
================================================
FILE: .npmrc
================================================
save-exact = true
strict-peer-dependencies=false
================================================
FILE: .yarnrc
================================================
save-prefix ""
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2023 Vercel, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: errors/invalid-entry.md
================================================
# Invalid Entry File
#### Why This Error Occurred
When the `micro` command was ran, you passed a path to a file or directory that contains invalid code. This code might either not be syntactically correct or throw an error on execution.
#### Possible Ways to Fix It
The only way to avoid this error is to ensure that the entry file to your microservice (the one passed to the `micro`) command contains code that doesn't contain any syntax errors and doesn't throw an error when executed.
### Useful Links
- [JSLint](http://www.jslint.com) - Validate the code of your entry file
================================================
FILE: errors/invalid-package-json.md
================================================
# Invalid `package.json` File
#### Why This Error Occurred
The content of the `package.json` file that's located near the entry file of your microservice is not valid. This means that it's not correct JSON syntax.
#### Possible Ways to Fix It
The only way to avoid this error is to ensure that the file contains a valid JSON object. You can use [JSONLint](https://jsonlint.com) to find any possible errors.
================================================
FILE: errors/invalid-port-socket.md
================================================
# Port and socket provided
#### Why This Error Occurred
When the `micro` command was ran, you passed both a port and a socket. Node.js can only listen to either a port or a socket, not both.
#### Possible Ways to Fix It
Only provide one of the arguments. If both are needed you can start 2 instances of micro with different arguments.
================================================
FILE: errors/invalid-server-port.md
================================================
# Invalid Server Port
#### Why This Error Occurred
When the `micro` command was ran, you supplied the port flag although it is
not a valid number.
#### Possible Ways to Fix It
The port must be a valid number between 1 and 65535. Although, remember some are
reserved to the operating system and others not in userland (only accessible
with administrator access).
================================================
FILE: errors/invalid-socket.md
================================================
# Invalid socket
#### Why This Error Occurred
When the `micro` command was ran, you passed `-s` or `--socket` without a value.
#### Possible Ways to Fix It
Run `micro` with a value:
```
micro -s '/tmp/micro.sock'
```
================================================
FILE: errors/no-export.md
================================================
# No Export
#### Why This Error Occurred
When `micro` tried to ran your microservice, it noticed that your code didn't export anything that could be run.
#### Possible Ways to Fix It
You need to ensure that the entry file you passed to the `micro` command contains an export - like this one:
```js
module.exports = (req, res) => {
res.end('test');
};
```
### Useful Links
- [List of examples](https://github.com/vercel/micro/tree/master/examples)
- [Usage information](https://github.com/vercel/micro#usage)
================================================
FILE: errors/path-missing.md
================================================
# Path Missing
#### Why This Error Occurred
When running the `micro` command, you need to pass a path to a file or directory that contains your microservice. If you don't define one, it will detect the entry file to your code using the `main` property inside the `package.json` file in the directory where the command is run.
#### Possible Ways to Fix It
- Enter the path to your microservice in the `main` property inside `package.json`
- Specify the path of your entry file when running the command: `micro <path>`
================================================
FILE: errors/path-not-existent.md
================================================
# Path Not Existent
#### Why This Error Occurred
When the `micro` command ran, you passed it a path to a file or directory that does't exist. This is how such a command can look like:
```bash
micro <not-existing-path>
```
#### Possible Ways to Fix It
The only way to fix this is to pass a path to a file or directory that exists and contains a working microservice.
================================================
FILE: examples/external-api-call/README.md
================================================
# External API call example
## How to use
Download the example [or clone the repo](https://github.com/vercel/micro):
```bash
curl https://codeload.github.com/vercel/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/external-api-call
cd external-api-call
```
Install it and run:
```bash
npm install
npm run start
```
## The idea behind the example
Shows how to get data from an external api using async/await.
================================================
FILE: examples/external-api-call/index.js
================================================
const fetch = require('node-fetch');
module.exports = async () => {
const response = await fetch('https://api.example.com');
const json = await response.json();
return json;
};
================================================
FILE: examples/external-api-call/package.json
================================================
{
"name": "external-api-call",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "micro"
},
"dependencies": {
"micro": "latest",
"node-fetch": "latest"
},
"author": "",
"license": "MIT"
}
================================================
FILE: examples/json-body-parsing/README.md
================================================
# Parse JSON body example
## How to use
Download the example [or clone the repo](https://github.com/vercel/micro):
```bash
curl https://codeload.github.com/vercel/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/json-body-parsing
cd json-body-parsing
```
Install it and run:
```bash
npm install
npm run start
```
## The idea behind the example
Shows how to get data posted to your microservice using async/await.
================================================
FILE: examples/json-body-parsing/index.js
================================================
const {json} = require('micro');
module.exports = async req => {
const data = await json(req);
console.log(data);
return 'Data logged to your console';
};
================================================
FILE: examples/json-body-parsing/package.json
================================================
{
"name": "json-body-parsing",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "micro"
},
"dependencies": {
"micro": "latest"
},
"author": "",
"license": "MIT"
}
================================================
FILE: examples/socket.io-chat-app/README.md
================================================
# Chat app with socket.io
## How to use
Download the example [or clone the repo](https://github.com/vercel/micro):
```bash
curl https://codeload.github.com/vercel/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/socket.io-chat-app
cd socket.io-chat-app
```
Install it and run:
```bash
npm install
npm run start
```
## The idea behind the example
Shows how to make use of socket.io with micro, to deploy on now.
================================================
FILE: examples/socket.io-chat-app/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Socket.IO Chat Example</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: grey; padding: 10px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.min.js"></script>
<script>
var socket = io()
document.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
socket.emit('chat message', document.getElementById('m').value);
document.getElementById('m').value = '';
});
socket.on('chat message', msg => {
document.getElementById('messages').insertAdjacentHTML('beforeend', `<li>${msg}</li>`);
});
</script>
</body>
</html>
================================================
FILE: examples/socket.io-chat-app/index.js
================================================
const micro = require('micro');
const fs = require('fs');
const path = require('path');
const document = path.join(__dirname, 'index.html');
const html = fs.readFileSync(document);
const server = micro(async (req, res) => {
console.log('Serving index.html');
res.end(html);
});
const io = require('socket.io')(server);
// socket-io handlers are in websocket-server.js
require('./websocket-server.js')(io);
server.listen(4000, () => console.log('Listening on localhost:4000'));
================================================
FILE: examples/socket.io-chat-app/package.json
================================================
{
"name": "chatnow",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node ."
},
"author": "Lucas Kostka",
"license": "ISC",
"dependencies": {
"micro": "latest",
"socket.io": "1.7.3"
}
}
================================================
FILE: examples/socket.io-chat-app/websocket-server.js
================================================
module.exports = function startServer(io) {
io.on('connection', socket => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
socket.on('chat message', msg => {
console.log(`message: ${msg}`);
io.emit('chat message', msg);
});
});
};
================================================
FILE: examples/urlencoded-body-parsing/README.md
================================================
# Parse JSON body example
## How to use
Download the example [or clone the repo](https://github.com/vercel/micro):
```bash
curl https://codeload.github.com/vercel/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/urlencoded-body-parsing
cd urlencoded-body-parsing
```
Install it and run:
```bash
npm install
npm run start
```
## The idea behind the example
Shows how to get urlencoded (html form post) data posted to your microservice using async/await.
================================================
FILE: examples/urlencoded-body-parsing/index.js
================================================
const parse = require('urlencoded-body-parser');
module.exports = async req => {
const data = await parse(req);
console.log(data);
return 'Data logged to your console';
};
================================================
FILE: examples/urlencoded-body-parsing/package.json
================================================
{
"name": "urlencoded-body-parsing",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "micro"
},
"dependencies": {
"micro": "latest",
"urlencoded-body-parser": "latest"
},
"author": "",
"license": "MIT"
}
================================================
FILE: examples/with-graphql-request/README.md
================================================
# GraphQL Request example
## How to use
Download the example [or clone the repo](https://github.com/vercel/micro):
```bash
curl https://codeload.github.com/vercel/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/with-graphql-request
cd with-graphql-request
```
Install it and run:
```bash
$ yarn install # (or `$ npm install`)
$ yarn run start # (or `$ npm run start`)
```
## The idea behind the example
Shows how to get data from a GraphQL endpoint using [GraphQL Request](https://github.com/graphcool/graphql-request).
This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
================================================
FILE: examples/with-graphql-request/index.js
================================================
const {request} = require('graphql-request');
const endpoint = 'https://api.graph.cool/simple/v1/movies';
// Prepare simple query
const query = `
query Movie($title: String!) {
movie: Movie(title: $title) {
releaseDate
actors {
name
}
}
}
`;
module.exports = async () => {
// Perform query
const data = await request(endpoint, query, {title: 'Inception'});
// Return Movie
return data.movie;
};
================================================
FILE: examples/with-graphql-request/package.json
================================================
{
"name": "with-graphql-request",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "micro"
},
"dependencies": {
"graphql-request": "latest",
"micro": "latest"
},
"license": "MIT"
}
================================================
FILE: examples/with-https/README.md
================================================
# Micro app with HTTPS
## How to use
Download the example [or clone the repo](https://github.com/vercel/micro):
```bash
curl https://codeload.github.com/vercel/micro/tar.gz/master | tar -xz --strip=2 micro-master/examples/with-https
cd socket.io-chat-app
```
Install it and run:
```bash
npm install
npm run start
```
## The idea behind the example
Shows how to make use of HTTPS requests with micro.
================================================
FILE: examples/with-https/index.js
================================================
const https = require('https');
const {run, send} = require('micro');
const {key, cert, passphrase} = require('openssl-self-signed-certificate');
const PORT = process.env.PORT || 3443;
const options = {key, cert, passphrase};
const microHttps = fn => https.createServer(options, (req, res) => run(req, res, fn));
const server = microHttps(async (req, res) => {
send(res, 200, {encrypted: req.client.encrypted});
});
server.listen(PORT);
console.log(`Listening on https://localhost:${PORT}`);
================================================
FILE: examples/with-https/package.json
================================================
{
"name": "with-https",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "node ."
},
"dependencies": {
"micro": "latest",
"openssl-self-signed-certificate": "^1.1.6"
}
}
================================================
FILE: lerna.json
================================================
{
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*"
],
"command": {
"version": {
"exact": true
},
"publish": {
"npmClient": "npm",
"allowBranch": [
"master",
"canary"
],
"registry": "https://registry.npmjs.org/"
}
},
"version": "9.4.0"
}
================================================
FILE: package.json
================================================
{
"private": true,
"workspaces": [
"packages/*",
"test"
],
"scripts": {
"prepublish": "lerna run prepublish",
"publish-canary": "lerna version prerelease --preid canary --force-publish && release --pre",
"publish-stable": "lerna version --force-publish",
"test": "cd test && yarn run test"
},
"license": "MIT",
"devDependencies": {
"lerna": "^3.4.0"
}
}
================================================
FILE: packages/micro/.eslintrc.js
================================================
module.exports = {
root: true,
extends: [
require.resolve('@vercel/style-guide/eslint/node'),
require.resolve('@vercel/style-guide/eslint/typescript'),
],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
ignorePatterns: ['dist/**', 'types/**'],
};
================================================
FILE: packages/micro/README.md
================================================
# Micro — Asynchronous HTTP microservices
## Features
- **Easy**: Designed for usage with `async` and `await`
- **Fast**: Ultra-high performance (even JSON parsing is opt-in)
- **Micro**: The whole project is ~260 lines of code
- **Agile**: Super easy deployment and containerization
- **Simple**: Oriented for single purpose modules (function)
- **Standard**: Just HTTP!
- **Explicit**: No middleware - modules declare all [dependencies](https://github.com/amio/awesome-micro)
- **Lightweight**: With all dependencies, the package weighs less than a megabyte
**Disclaimer:** Micro was created for use within containers and is not intended for use in serverless environments. For those using Vercel, this means that there is no requirement to use Micro in your projects as the benefits it provides are not applicable to the platform. Utility features provided by Micro, such as `json`, are readily available in the form of [Serverless Function helpers](https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-request-and-response-objects).
## Installation
**Important:** Micro is only meant to be used in production. In development, you should use [micro-dev](https://github.com/vercel/micro-dev), which provides you with a tool belt specifically tailored for developing microservices.
To prepare your microservice for running in the production environment, firstly install `micro`:
```bash
npm install --save micro
```
## Usage
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:
```js
module.exports = (req, res) => {
res.end('Welcome to Micro');
};
```
Micro provides [useful helpers](https://github.com/vercel/micro#body-parsing) but also handles return values – so you can write it even shorter!
```js
module.exports = () => 'Welcome to Micro';
```
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:
```json
{
"main": "index.js",
"scripts": {
"start": "micro"
}
}
```
Once all of that is done, the server can be started like this:
```bash
npm start
```
And go to this URL: `http://localhost:3000` - 🎉
### Command line
```
micro - Asynchronous HTTP microservices
USAGE
$ micro --help
$ micro --version
$ micro [-l listen_uri [-l ...]] [entry_point.js]
By default micro will listen on 0.0.0.0:3000 and will look first
for the "main" property in package.json and subsequently for index.js
as the default entry_point.
Specifying a single --listen argument will overwrite the default, not supplement it.
OPTIONS
--help shows this help message
-v, --version displays the current version of micro
-l, --listen listen_uri specify a URI endpoint on which to listen (see below) -
more than one may be specified to listen in multiple places
ENDPOINTS
Listen endpoints (specified by the --listen or -l options above) instruct micro
to listen on one or more interfaces/ports, UNIX domain sockets, or Windows named pipes.
For TCP (traditional host/port) endpoints:
$ micro -l tcp://hostname:1234
For UNIX domain socket endpoints:
$ micro -l unix:/path/to/socket.sock
For Windows named pipe endpoints:
$ micro -l pipe:\\.\pipe\PipeName
```
### `async` & `await`
<p><details>
<summary><b>Examples</b></summary>
<ul><li><a href="./examples/external-api-call">Fetch external api</a></li></ul>
</details></p>
Micro is built for usage with async/await.
```js
const sleep = require('then-sleep');
module.exports = async (req, res) => {
await sleep(500);
return 'Ready!';
};
```
### Port Based on Environment Variable
When you want to set the port using an environment variable you can use:
```
micro -l tcp://0.0.0.0:$PORT
```
Optionally you can add a default if it suits your use case:
```
micro -l tcp://0.0.0.0:${PORT-3000}
```
`${PORT-3000}` will allow a fallback to port `3000` when `$PORT` is not defined.
Note that this only works in Bash.
### Body parsing
<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>
For parsing the incoming request body we included an async functions `buffer`, `text` and `json`
```js
const { buffer, text, json } = require('micro');
module.exports = async (req, res) => {
const buf = await buffer(req);
console.log(buf);
// <Buffer 7b 22 70 72 69 63 65 22 3a 20 39 2e 39 39 7d>
const txt = await text(req);
console.log(txt);
// '{"price": 9.99}'
const js = await json(req);
console.log(js.price);
// 9.99
return '';
};
```
### API
##### `buffer(req, { limit = '1mb', encoding = 'utf8' })`
##### `text(req, { limit = '1mb', encoding = 'utf8' })`
##### `json(req, { limit = '1mb', encoding = 'utf8' })`
- Buffers and parses the incoming body and returns it.
- Exposes an `async` function that can be run with `await`.
- Can be called multiple times, as it caches the raw request body the first time.
- `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'`.
- If JSON parsing fails, an `Error` is thrown with `statusCode` set to `400` (see [Error Handling](#error-handling))
For other types of data check the [examples](#body-parsing-examples)
### 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);
};
```
##### `send(res, statusCode, data = null)`
- Use `require('micro').send`.
- `statusCode` is a `Number` with the HTTP status code, and must always be supplied.
- 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.
- `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).
- `Buffer`: `data` is written as an `octet-stream`.
- `object`: `data` is serialized as JSON.
- `string`: `data` is written as-is.
- If JSON serialization fails (for example, if a cyclical reference is found), a `400` error is thrown. See [Error Handling](#error-handling).
### Programmatic use
You can use Micro programmatically by requiring Micro directly:
```js
const http = require('http');
const sleep = require('then-sleep');
const { serve } = require('micro');
const server = new http.Server(
serve(async (req, res) => {
await sleep(500);
return 'Hello world';
}),
);
server.listen(3000);
```
##### serve(fn)
- Use `require('micro').serve`.
- Returns a function with the `(req, res) => void` signature. 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.
- 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).
## Error Handling
Micro allows you to write robust microservices. This is accomplished primarily by bringing sanity back to error handling and avoiding callback soup.
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.
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:
```js
const rateLimit = require('my-rate-limit');
module.exports = async (req, res) => {
await rateLimit(req);
// ... your code
};
```
If the API endpoint is abused, it can throw an error with `createError` like so:
```js
if (tooMany) {
throw createError(429, 'Rate limit exceeded');
}
```
Alternatively you can create the `Error` object yourself
```js
if (tooMany) {
const err = new Error('Rate limit exceeded');
err.statusCode = 429;
throw err;
}
```
The nice thing about this model is that the `statusCode` is merely a suggestion. The user can override it:
```js
try {
await rateLimit(req);
} catch (err) {
if (429 == err.statusCode) {
// perhaps send 500 instead?
send(res, 500);
}
}
```
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`.
In order to set up your own error handling mechanism, you can use composition in your handler:
```js
const { send } = require('micro');
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?');
});
```
## Testing
Micro makes tests compact and a pleasure to read and write.
We recommend [Node TAP](https://node-tap.org/) or [AVA](https://github.com/avajs/ava), a highly parallel test framework with built-in support for async tests:
```js
const http = require('http');
const { send, serve } = require('micro');
const test = require('ava');
const listen = require('test-listen');
const fetch = require('node-fetch');
test('my endpoint', async (t) => {
const service = new http.Server(
serve(async (req, res) => {
send(res, 200, {
test: 'woot',
});
}),
);
const url = await listen(service);
const response = await fetch(url);
const body = await response.json();
t.deepEqual(body.test, 'woot');
service.close();
});
```
Look at [test-listen](https://github.com/vercel/test-listen) for a
function that returns a URL with an ephemeral port every time it's called.
## Contributing
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
2. Link the package to the global module directory: `npm link`
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!
You can run the tests using: `npm test`.
## Credits
Thanks to Tom Yandell and Richard Hodgson for donating the name "micro" on [npm](https://www.npmjs.com)!
## Authors
- Guillermo Rauch ([@rauchg](https://x.com/rauchg)) - [Vercel](https://vercel.com)
- Leo Lamprecht ([@leo](https://x.com/leo)) - [Vercel](https://vercel.com)
- Tim Neutkens ([@timneutkens](https://x.com/timneutkens)) - [Vercel](https://vercel.com)
================================================
FILE: packages/micro/package.json
================================================
{
"name": "micro",
"version": "10.0.1",
"description": "Asynchronous HTTP microservices",
"license": "MIT",
"main": "./dist/src/lib/index.js",
"types": "./types/src/lib",
"files": [
"src",
"dist",
"types"
],
"bin": {
"micro": "./dist/src/bin/micro.js"
},
"engines": {
"node": ">= 16.0.0"
},
"scripts": {
"build": "tsc",
"prepublishOnly": "yarn run build",
"eslint-check": "eslint --max-warnings=0 .",
"prettier-check": "prettier --check .",
"type-check": "tsc --noEmit"
},
"repository": "vercel/micro",
"keywords": [
"micro",
"service",
"microservice",
"serverless",
"API"
],
"dependencies": {
"arg": "4.1.0",
"content-type": "1.0.4",
"raw-body": "2.4.1"
},
"devDependencies": {
"@types/content-type": "1.1.5",
"@types/node": "18.0.3",
"@vercel/style-guide": "3.0.0",
"eslint": "8.19.0",
"prettier": "2.7.1",
"typescript": "4.7.4"
},
"prettier": "@vercel/style-guide/prettier"
}
================================================
FILE: packages/micro/src/bin/micro.ts
================================================
#!/usr/bin/env node
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-console */
// Native
import Module from 'module';
import http from 'http';
import path from 'path';
import { existsSync } from 'fs';
// Packages
import arg from 'arg';
// Utilities
import { serve } from '../lib';
import { handle } from '../lib/handler';
import { version } from '../../package.json';
import { logError } from '../lib/error';
import { parseEndpoint } from '../lib/parse-endpoint';
import type { AddressInfo } from 'net';
import type { RequestHandler } from '../lib';
// Check if the user defined any options
const args = arg({
'--listen': parseEndpoint,
'-l': '--listen',
'--help': Boolean,
'--version': Boolean,
'-v': '--version',
});
// When `-h` or `--help` are used, print out
// the usage information
if (args['--help']) {
console.error(`
micro - Asynchronous HTTP microservices
USAGE
$ micro --help
$ micro --version
$ micro [-l listen_uri [-l ...]] [entry_point.js]
By default micro will listen on 0.0.0.0:3000 and will look first
for the "main" property in package.json and subsequently for index.js
as the default entry_point.
Specifying a single --listen argument will overwrite the default, not supplement it.
OPTIONS
--help shows this help message
-v, --version displays the current version of micro
-l, --listen listen_uri specify a URI endpoint on which to listen (see below) -
more than one may be specified to listen in multiple places
ENDPOINTS
Listen endpoints (specified by the --listen or -l options above) instruct micro
to listen on one or more interfaces/ports, UNIX domain sockets, or Windows named pipes.
For TCP (traditional host/port) endpoints:
$ micro -l tcp://hostname:1234
For UNIX domain socket endpoints:
$ micro -l unix:/path/to/socket.sock
For Windows named pipe endpoints:
$ micro -l pipe:\\\\.\\pipe\\PipeName
`);
process.exit(2);
}
// Print out the package's version when
// `--version` or `-v` are used
if (args['--version']) {
console.log(version);
process.exit();
}
if (!args['--listen']) {
// default endpoint
args['--listen'] = [String(3000)];
}
let file = args._[0];
if (!file) {
try {
const req = Module.createRequire(module.filename);
const packageJson: unknown = req(
path.resolve(process.cwd(), 'package.json'),
);
if (hasMain(packageJson)) {
file = packageJson.main;
} else {
file = 'index.js';
}
} catch (err) {
if (isNodeError(err) && err.code !== 'MODULE_NOT_FOUND') {
logError(
`Could not read \`package.json\`: ${err.message}`,
'invalid-package-json',
);
process.exit(1);
}
}
}
if (!file) {
logError('Please supply a file!', 'path-missing');
process.exit(1);
}
if (!file.startsWith('/')) {
file = path.resolve(process.cwd(), file);
}
if (!existsSync(file)) {
logError(
`The file or directory "${path.basename(file)}" doesn't exist!`,
'path-not-existent',
);
process.exit(1);
}
function registerShutdown(fn: () => void) {
let run = false;
const wrapper = () => {
if (!run) {
run = true;
fn();
}
};
process.on('SIGINT', wrapper);
process.on('SIGTERM', wrapper);
process.on('exit', wrapper);
}
function startEndpoint(module: RequestHandler, endpoint: string) {
const server = new http.Server(serve(module));
server.on('error', (err) => {
console.error('micro:', err.stack);
process.exit(1);
});
server.listen(endpoint, () => {
const details = server.address();
registerShutdown(() => {
console.log('micro: Gracefully shutting down. Please wait...');
server.close();
process.exit();
});
// `micro` is designed to run only in production, so
// this message is perfect for prod
if (typeof details === 'string') {
console.log(`micro: Accepting connections on ${details}`);
} else if (isAddressInfo(details)) {
console.log(`micro: Accepting connections on port ${details.port}`);
} else {
console.log('micro: Accepting connections');
}
});
}
async function start() {
if (file && args['--listen']) {
const loadedModule = await handle(file);
for (const endpoint of args['--listen']) {
startEndpoint(loadedModule as RequestHandler, endpoint);
}
}
}
start()
.then()
.catch((error) => {
if (error instanceof Error) {
logError(error.message, 'STARTUP_FAILURE');
}
process.exit(1);
});
function hasMain(packageJson: unknown): packageJson is { main: string } {
return (
typeof packageJson === 'object' &&
packageJson !== null &&
'main' in packageJson
);
}
function isNodeError(
error: unknown,
): error is { code: string; message: string } {
return error instanceof Error && 'code' in error;
}
function isAddressInfo(obj: unknown): obj is AddressInfo {
return 'port' in (obj as AddressInfo);
}
================================================
FILE: packages/micro/src/lib/error.ts
================================================
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable no-console */
export function logError(message: string, errorCode: string) {
console.error(`micro: ${message}`);
console.error(`micro: https://err.sh/micro/${errorCode}`);
}
================================================
FILE: packages/micro/src/lib/handler.ts
================================================
// Utilities
import { logError } from './error';
export const handle = async (file: string) => {
let mod: unknown;
try {
mod = await import(file);
mod = await (mod as { default: unknown }).default; // use ES6 module's default export
} catch (err: unknown) {
if (isErrorObject(err) && err.stack) {
logError(`Error when importing ${file}: ${err.stack}`, 'invalid-entry');
}
process.exit(1);
}
if (typeof mod !== 'function') {
logError(`The file "${file}" does not export a function.`, 'no-export');
process.exit(1);
}
return mod;
};
function isErrorObject(error: unknown): error is Error {
return (error as Error).stack !== undefined;
}
================================================
FILE: packages/micro/src/lib/index.ts
================================================
// Native
import { Stream, Readable } from 'stream';
// Packages
import contentType from 'content-type';
import getRawBody from 'raw-body';
import type { RawBodyError } from 'raw-body';
//Types
import type { IncomingMessage, ServerResponse, RequestListener } from 'http';
// slight modification of is-stream https://github.com/sindresorhus/is-stream/blob/c918e3795ea2451b5265f331a00fb6a8aaa27816/license
function isStream(stream: unknown): stream is Stream {
return (
stream !== null &&
typeof stream === 'object' &&
stream instanceof Stream &&
typeof stream.pipe === 'function'
);
}
function readable(stream: unknown): stream is Readable {
return (
isStream(stream) && // TODO: maybe this isn't needed because we could use only the checks below
stream instanceof Readable &&
stream.readable
);
}
export type RequestHandler = (
req: IncomingMessage,
res: ServerResponse,
) => unknown;
type Serve = (fn: RequestHandler) => RequestListener;
export const serve: Serve = (fn) => (req, res) => run(req, res, fn);
export class HttpError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, HttpError.prototype);
}
statusCode?: number;
originalError?: Error;
}
function isError(error: unknown): error is Error | HttpError {
return error instanceof Error || error instanceof HttpError;
}
export const createError = (code: number, message: string, original: Error) => {
const err = new HttpError(message);
err.statusCode = code;
err.originalError = original;
return err;
};
export const send = (
res: ServerResponse,
code: number,
obj: unknown = null,
) => {
res.statusCode = code;
if (obj === null) {
res.end();
return;
}
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);
return;
}
if (obj instanceof Stream || readable(obj)) {
//TODO: Wouldn't (obj instanceof Stream) be the only check here? Do we specifically need a Readable stream or a Stream object that's not of NodeJS Stream?
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream');
}
obj.pipe(res);
return;
}
let str = obj;
if (typeof obj === 'object' || typeof obj === 'number') {
// We stringify before setting the header
// in case `JSON.stringify` throws and a
// 500 has to be sent instead
str = JSON.stringify(obj);
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
}
}
if (typeof str === 'string') {
res.setHeader('Content-Length', Buffer.byteLength(str));
}
res.end(str);
};
export const sendError = (
req: IncomingMessage,
res: ServerResponse,
errorObj: Error | HttpError,
) => {
if ('statusCode' in errorObj && errorObj.statusCode) {
send(res, errorObj.statusCode, errorObj.message);
} else send(res, 500, 'Internal Server Error');
if (errorObj instanceof Error) {
// eslint-disable-next-line no-console
console.error(errorObj.stack);
} else {
// eslint-disable-next-line no-console
console.warn('thrown error must be an instance Error');
}
};
export const run = (
req: IncomingMessage,
res: ServerResponse,
fn: RequestHandler,
) =>
new Promise((resolve) => {
resolve(fn(req, res));
})
.then((val) => {
if (val === null) {
send(res, 204, null);
return;
}
// Send value if it is not undefined, otherwise assume res.end
// will be called later
if (val !== undefined) {
send(res, res.statusCode || 200, val);
}
})
.catch((err: unknown) => {
if (isError(err)) {
sendError(req, res, err);
}
});
// Maps requests to buffered raw bodies so that
// multiple calls to `json` work as expected
const rawBodyMap = new WeakMap<IncomingMessage, Buffer>();
const parseJSON = (str: string): unknown => {
try {
return JSON.parse(str);
} catch (err: unknown) {
throw createError(400, 'Invalid JSON', err as Error);
}
};
export interface BufferInfo {
limit?: string | number | undefined;
encoding?: BufferEncoding;
}
function isRawBodyError(error: unknown): error is RawBodyError {
return 'type' in (error as RawBodyError);
}
export const buffer = (
req: IncomingMessage,
{ limit = '1mb', encoding }: BufferInfo = {},
) =>
Promise.resolve().then(() => {
const type = req.headers['content-type'] || 'text/plain';
const length = req.headers['content-length'];
const body = rawBodyMap.get(req);
if (body) {
return body;
}
return getRawBody(req, {
limit,
length,
encoding: encoding ?? contentType.parse(type).parameters.charset,
})
.then((buf) => {
rawBodyMap.set(req, buf);
return buf;
})
.catch((err) => {
if (isRawBodyError(err) && err.type === 'entity.too.large') {
throw createError(413, `Body exceeded ${limit} limit`, err);
} else {
throw createError(400, 'Invalid body', err as Error);
}
});
});
export const text = (
req: IncomingMessage,
{ limit, encoding }: BufferInfo = {},
) => buffer(req, { limit, encoding }).then((body) => body.toString(encoding));
export const json = (req: IncomingMessage, opts: BufferInfo = {}) =>
text(req, opts).then((body) => parseJSON(body));
================================================
FILE: packages/micro/src/lib/parse-endpoint.ts
================================================
export function parseEndpoint(endpoint: string) {
const url = new URL(endpoint);
switch (url.protocol) {
case 'pipe:': {
// some special handling
const cutStr = endpoint.replace(/^pipe:/, '');
if (!cutStr.startsWith('\\\\.\\')) {
throw new Error(`Invalid Windows named pipe endpoint: ${endpoint}`);
}
return [cutStr];
}
case 'unix:':
if (!url.pathname) {
throw new Error(`Invalid UNIX domain socket endpoint: ${endpoint}`);
}
return [url.pathname];
case 'tcp:':
url.port = url.port || '3000';
return [parseInt(url.port, 10).toString(), url.hostname];
default:
throw new Error(
`Unknown --listen endpoint scheme (protocol): ${url.protocol}`,
);
}
}
================================================
FILE: packages/micro/tsconfig.json
================================================
{
"extends": "@vercel/style-guide/typescript",
"compilerOptions": {
"target": "ES2021",
"module": "CommonJS",
"moduleResolution": "Node16",
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "dist",
"declaration": true,
"declarationDir": "./types",
"declarationMap": true,
"removeComments": true
},
"include": ["src"]
}
================================================
FILE: packages/micro/types/src/bin/micro.d.ts
================================================
#!/usr/bin/env node
export {};
//# sourceMappingURL=micro.d.ts.map
================================================
FILE: packages/micro/types/src/lib/error.d.ts
================================================
export declare function logError(message: string, errorCode: string): void;
//# sourceMappingURL=error.d.ts.map
================================================
FILE: packages/micro/types/src/lib/handler.d.ts
================================================
export declare const handle: (file: string) => Promise<Function>;
//# sourceMappingURL=handler.d.ts.map
================================================
FILE: packages/micro/types/src/lib/index.d.ts
================================================
/// <reference types="node" />
/// <reference types="node" />
import type { IncomingMessage, ServerResponse, RequestListener } from 'http';
export declare type RequestHandler = (req: IncomingMessage, res: ServerResponse) => unknown;
declare type Serve = (fn: RequestHandler) => RequestListener;
export declare const serve: Serve;
export declare class HttpError extends Error {
constructor(message: string);
statusCode?: number;
originalError?: Error;
}
export declare const createError: (code: number, message: string, original: Error) => HttpError;
export declare const send: (res: ServerResponse, code: number, obj?: unknown) => void;
export declare const sendError: (req: IncomingMessage, res: ServerResponse, errorObj: Error | HttpError) => void;
export declare const run: (req: IncomingMessage, res: ServerResponse, fn: RequestHandler) => Promise<void>;
export interface BufferInfo {
limit?: string | number | undefined;
encoding?: BufferEncoding;
}
export declare const buffer: (req: IncomingMessage, { limit, encoding }?: BufferInfo) => Promise<Buffer>;
export declare const text: (req: IncomingMessage, { limit, encoding }?: BufferInfo) => Promise<string>;
export declare const json: (req: IncomingMessage, opts?: BufferInfo) => Promise<unknown>;
export {};
//# sourceMappingURL=index.d.ts.map
================================================
FILE: packages/micro/types/src/lib/parse-endpoint.d.ts
================================================
export declare function parseEndpoint(endpoint: string): string[];
//# sourceMappingURL=parse-endpoint.d.ts.map
================================================
FILE: test/.eslintrc.js
================================================
module.exports = {
root: true,
extends: [
require.resolve('@vercel/style-guide/eslint/node'),
require.resolve('@vercel/style-guide/eslint/typescript'),
],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
};
================================================
FILE: test/package.json
================================================
{
"name": "test",
"version": "1.0.0",
"engines": {
"node": ">= 16.0.0"
},
"scripts": {
"eslint-check": "eslint --max-warnings=0 .",
"prettier-check": "prettier --check .",
"type-check": "tsc --noEmit",
"test": "tap --ts suite/"
},
"devDependencies": {
"@types/node-fetch": "2.6.2",
"@types/sinon": "10.0.13",
"@types/tap": "15.0.7",
"@vercel/style-guide": "3.0.0",
"eslint": "8.19.0",
"node-fetch": "2.6.6",
"prettier": "2.7.1",
"sinon": "14.0.0",
"tap": "16.3.0",
"ts-node": "10.9.1",
"typescript": "4.7.4",
"micro": "*"
},
"prettier": "@vercel/style-guide/prettier"
}
================================================
FILE: test/suite/handler.ts
================================================
import { test } from 'tap';
import { handle } from 'micro/src/lib/handler';
import { stub } from 'sinon';
void test('handle a non-async function', async (t) => {
const dir = t.testdir({
'regular-function-export.js': `module.exports = () => 'Test';`,
});
const result = await handle(`${dir}/regular-function-export.js`);
t.type(result, 'function');
});
void test('handle async function', async (t) => {
const dir = t.testdir({
'promise-export.js': `module.exports = async () => 'Test';`,
});
const result = await handle(`${dir}/promise-export.js`);
t.type(result, 'function');
});
void test(`handle ESM's non-async function`, async (t) => {
const dir = t.testdir({
'esm-function-export.mjs': `export default () => 'Hello ESM';`,
});
const result = await handle(`${dir}/esm-function-export.mjs`);
t.type(result, 'function');
});
void test(`handle ESM's async function`, async (t) => {
const dir = t.testdir({
'esm-async-export.mjs': `export default async () => 'Hello ESM';`,
});
const result = await handle(`${dir}/esm-async-export.mjs`);
t.type(result, 'function');
});
void test('process.exit when handling an invalid export', async (t) => {
const dir = t.testdir({
'regular-object.js': `module.exports = {};`,
});
const processStub = stub(process, 'exit').callsFake(() => {
throw new Error('Fake');
});
await t.rejects(handle(`${dir}/regular-object.js`), { message: 'Fake' });
t.equal(processStub.calledOnceWith(1), true);
processStub.restore();
});
void test('process.exit when handling and inexisting file', async (t) => {
const dir = t.testdir();
const processStub = stub(process, 'exit').callsFake(() => {
throw new Error('Fake');
});
await t.rejects(handle(`${dir}/foo/bar`), { message: 'Fake' });
t.equal(processStub.calledOnceWith(1), true);
processStub.restore();
});
================================================
FILE: test/suite/index.ts
================================================
import http from 'http';
import Stream from 'stream';
import { Socket } from 'net';
import { stub } from 'sinon';
import { test } from 'tap';
import {
serve,
run,
send,
sendError,
buffer,
json,
HttpError,
} from 'micro/src/lib/index';
import fetch from 'node-fetch';
import type { AddressInfo } from 'net';
import type { RequestHandler, BufferInfo } from 'micro/src/lib/index';
function startServer(handler: RequestHandler): Promise<[string, () => void]> {
return new Promise((resolve, reject) => {
const server = http.createServer(serve(handler));
server.on('error', reject);
server.listen(() => {
const { port } = server.address() as AddressInfo;
resolve([
`http://localhost:${port}`,
() => {
server.close();
},
]);
});
});
}
function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
void test('send(200, <String>)', async (t) => {
const fn: RequestHandler = (req, res) => {
send(res, 200, 'woot');
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
const body = await res.text();
t.same(body, 'woot');
shutdown();
});
void test('send(200, <Object>)', async (t) => {
const fn: RequestHandler = (req, res) => {
send(res, 200, {
a: 'b',
});
};
const [url, shutdown] = await startServer(fn);
const res: unknown = await fetch(url).then((r) => r.json());
t.same(res, {
a: 'b',
});
shutdown();
});
void test('send(200, <Number>)', async (t) => {
const fn: RequestHandler = (req, res) => {
// Chosen by fair dice roll. guaranteed to be random.
send(res, 200, 4);
};
const [url, shutdown] = await startServer(fn);
const res: unknown = await fetch(url).then((r) => r.json());
t.same(res, 4);
shutdown();
});
void test('send(200, <Buffer>)', async (t) => {
const fn: RequestHandler = (req, res) => {
send(res, 200, Buffer.from('muscle'));
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'muscle');
shutdown();
});
void test('send(200, <Stream>)', async (t) => {
const fn: RequestHandler = (req, res) => {
send(res, 200, 'waterfall');
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'waterfall');
shutdown();
});
void test('send(<Number>)', async (t) => {
const fn: RequestHandler = (req, res) => {
send(res, 404);
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 404);
shutdown();
});
void test('return <String>', async (t) => {
const fn: RequestHandler = () => 'woot';
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'woot');
shutdown();
});
void test('return <Promise>', async (t) => {
const fn: RequestHandler = async () => {
await sleep(100);
return 'I Promise';
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'I Promise');
shutdown();
});
void test('sync return <String>', async (t) => {
const fn: RequestHandler = () => 'argon';
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'argon');
shutdown();
});
void test('return empty string', async (t) => {
const fn: RequestHandler = () => '';
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, '');
shutdown();
});
void test('return <Object>', async (t) => {
const fn: RequestHandler = () => ({
a: 'b',
});
const [url, shutdown] = await startServer(fn);
const res: unknown = await fetch(url).then((r) => r.json());
t.same(res, {
a: 'b',
});
shutdown();
});
void test('return <Number>', async (t) => {
const fn: RequestHandler = () =>
// Chosen by fair dice roll. guaranteed to be random.
4;
const [url, shutdown] = await startServer(fn);
const res: unknown = await fetch(url).then((r) => r.json());
t.same(res, 4);
shutdown();
});
void test('return <Buffer>', async (t) => {
const fn: RequestHandler = () => Buffer.from('Hammer');
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'Hammer');
shutdown();
});
void test('return <Stream>', async (t) => {
const fn: RequestHandler = () => {
const stream = new Stream.Transform();
stream.push('River');
stream.end();
return stream;
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'River');
shutdown();
});
void test('return <null>', async (t) => {
const fn: RequestHandler = () => null;
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
const body = await res.text();
t.equal(res.status, 204);
t.equal(body, '');
shutdown();
});
void test('return <null> calls res.end once', async (t) => {
const fn: RequestHandler = () => null;
const req = new http.IncomingMessage(new Socket());
const res = new http.ServerResponse(req);
const fake = stub(res, 'end');
await run(req, res, fn);
t.equal(fake.calledOnce, true);
});
void test('throw with code', async (t) => {
const fn: RequestHandler = async () => {
await sleep(100);
const err = new HttpError('Error from test (expected)');
err.statusCode = 402;
throw err;
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 402);
shutdown();
});
void test('throw (500)', async (t) => {
const fn: RequestHandler = () => {
throw new Error('500 from test (expected)');
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 500);
shutdown();
});
void test('throw (500) sync', async (t) => {
const fn: RequestHandler = () => {
throw new Error('500 from test (expected)');
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 500);
shutdown();
});
void test('send(200, <Stream>) with error on same tick', async (t) => {
const fn: RequestHandler = (req, res) => {
const stream = new Stream.Transform();
stream.push('error-stream');
stream.emit('error', new Error('500 from test (expected)'));
stream.end();
send(res, 200, stream);
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 500);
shutdown();
});
void test('custom error', async (t) => {
const fn: RequestHandler = async () => {
await sleep(50);
throw new Error('500 from test (expected)');
};
const handleErrors =
(ofn: RequestHandler) =>
async (req: http.IncomingMessage, res: http.ServerResponse) => {
try {
return await ofn(req, res);
} catch (err) {
send(res, 200, 'My custom error!');
}
};
const [url, shutdown] = await startServer(handleErrors(fn));
const res = await fetch(url).then((r) => r.text());
t.same(res, 'My custom error!');
shutdown();
});
void test('custom async error', async (t) => {
const fn: RequestHandler = async () => {
await sleep(50);
throw new Error('500 from test (expected)');
};
const handleErrors =
(ofn: RequestHandler) =>
async (req: http.IncomingMessage, res: http.ServerResponse) => {
try {
return await ofn(req, res);
} catch (err) {
send(res, 200, 'My custom error!');
}
};
const [url, shutdown] = await startServer(handleErrors(fn));
const res = await fetch(url).then((r) => r.text());
t.same(res, 'My custom error!');
shutdown();
});
void test('json parse error', async (t) => {
const fn: RequestHandler = async (req, res) => {
const body = await json(req);
send(res, 200, (body as { woot: string }).woot);
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url, {
method: 'POST',
body: '{ "bad json" }',
headers: {
'Content-Type': 'application/json',
},
});
t.same(status, 400);
shutdown();
});
void test('json', async (t) => {
interface Payload {
some: { cool: string };
}
const fn: RequestHandler = async (req, res) => {
const body = await json(req);
send(res, 200, {
response: (body as Payload).some.cool,
});
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({
some: {
cool: 'json',
},
}),
});
const body: unknown = await res.json();
t.same((body as { response: unknown }).response, 'json');
shutdown();
});
void test('json limit (below)', async (t) => {
interface Payload {
some: { cool: string };
}
const fn: RequestHandler = async (req, res) => {
const body = await json(req, {
limit: 100,
});
send(res, 200, {
response: (body as Payload).some.cool,
});
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({
some: {
cool: 'json',
},
}),
});
const body: unknown = await res.json();
t.same((body as { response: unknown }).response, 'json');
shutdown();
});
void test('json limit (over)', async (t) => {
const fn: RequestHandler = async (req, res) => {
try {
await json(req, {
limit: 3,
});
} catch (err) {
t.same((err as HttpError).statusCode, 413);
}
send(res, 200, 'ok');
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({
some: {
cool: 'json',
},
}),
});
t.same(res.status, 200);
shutdown();
});
void test('json circular', async (t) => {
interface Payload {
circular: boolean;
obj?: Payload;
}
const fn: RequestHandler = (req, res) => {
const obj: Payload = {
circular: true,
};
obj.obj = obj;
send(res, 200, obj);
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 500);
shutdown();
});
void test('no async', async (t) => {
const fn: RequestHandler = (req, res) => {
send(res, 200, {
a: 'b',
});
};
const [url, shutdown] = await startServer(fn);
const obj: unknown = await fetch(url).then((r) => r.json());
t.same((obj as { a: string }).a, 'b');
shutdown();
});
void test('limit included in error', async (t) => {
interface Payload {
some: { cool: string };
}
const fn: RequestHandler = async (req, res) => {
let body;
try {
body = await json(req, {
limit: 3,
});
} catch (err) {
t.ok((err as Error).message.includes('exceeded 3 limit'));
}
send(res, 200, {
response: (body as Payload).some.cool,
});
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({
some: {
cool: 'json',
},
}),
});
t.same(res.status, 500);
shutdown();
});
void test('support for status fallback in errors', async (t) => {
const fn: RequestHandler = (req, res) => {
const err = new HttpError('Custom');
err.statusCode = 403;
sendError(req, res, err);
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 403);
shutdown();
});
void test('json from rawBodyMap works', async (t) => {
interface Payload {
some: { cool: string };
}
const fn: RequestHandler = async (req, res) => {
const bodyOne = await json(req);
const bodyTwo = await json(req);
t.same(bodyOne, bodyTwo);
send(res, 200, {
response: (bodyOne as Payload).some.cool,
});
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({
some: {
cool: 'json',
},
}),
});
const body: unknown = await res.json();
t.same((body as { response: unknown }).response, 'json');
shutdown();
});
void test('statusCode defaults to 200', async (t) => {
const fn: RequestHandler = () => {
return 'woot';
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
const body = await res.text();
t.equal(body, 'woot');
t.equal(res.status, 200);
shutdown();
});
void test('statusCode on response works', async (t) => {
const fn: RequestHandler = (req, res) => {
res.statusCode = 400;
return 'woot';
};
const [url, shutdown] = await startServer(fn);
const { status } = await fetch(url);
t.same(status, 400);
shutdown();
});
void test('Content-Type header is preserved on string', async (t) => {
const fn: RequestHandler = (req, res) => {
res.setHeader('Content-Type', 'text/html');
return '<blink>woot</blink>';
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
t.equal(res.headers.get('content-type'), 'text/html');
shutdown();
});
void test('Content-Type header is preserved on stream', async (t) => {
const fn: RequestHandler = (req, res) => {
res.setHeader('Content-Type', 'text/html');
const stream = new Stream.Transform();
stream.push('River');
stream.end();
return stream;
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
t.equal(res.headers.get('content-type'), 'text/html');
shutdown();
});
void test('Content-Type header is preserved on buffer', async (t) => {
const fn: RequestHandler = (req, res) => {
res.setHeader('Content-Type', 'text/html');
return Buffer.from('hello');
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
t.equal(res.headers.get('content-type'), 'text/html');
shutdown();
});
void test('Content-Type header is preserved on object', async (t) => {
const fn: RequestHandler = (req, res) => {
res.setHeader('Content-Type', 'text/html');
return {};
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
t.equal(res.headers.get('content-type'), 'text/html');
shutdown();
});
void test('res.end is working', async (t) => {
const fn: RequestHandler = (req, res) => {
setTimeout(() => res.end('woot'), 100);
};
const [url, shutdown] = await startServer(fn);
const res = await fetch(url).then((r) => r.text());
t.same(res, 'woot');
shutdown();
});
void test('json should throw 400 on empty body with no headers', async (t) => {
const fn: RequestHandler = (req) => json(req);
const [url, shutdown] = await startServer(fn);
const res = await fetch(url);
const body = await res.text();
t.equal(body, 'Invalid JSON');
t.equal(res.status, 400);
shutdown();
});
void test('buffer should throw 400 on invalid encoding', async (t) => {
const bufferInfo = { encoding: 'lol' };
const fn: RequestHandler = async (req) =>
buffer(req, bufferInfo as BufferInfo);
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, {
method: 'POST',
body: '❤️',
});
const body = await res.text();
t.equal(body, 'Invalid body');
t.equal(res.status, 400);
shutdown();
});
void test('buffer works', async (t) => {
const fn: RequestHandler = (req) => buffer(req);
const [url, shutdown] = await startServer(fn);
const res = await fetch(url, { method: 'POST', body: '❤️' });
const body = await res.text();
t.equal(body, '❤️');
shutdown();
});
void test('Content-Type header for JSON is set', async (t) => {
const [url, shutdown] = await startServer(() => ({}));
const res = await fetch(url);
t.equal(res.headers.get('content-type'), 'application/json; charset=utf-8');
shutdown();
});
================================================
FILE: test/suite/parse-endpoint.ts
================================================
import { test } from 'tap';
import { parseEndpoint } from 'micro/src/lib/parse-endpoint';
void test('parses TCP URI', (t) => {
t.same(parseEndpoint('tcp://my-host-name.foo.bar:12345'), [
12345,
'my-host-name.foo.bar',
]);
t.same(parseEndpoint('tcp://0.0.0.0:8080'), [8080, '0.0.0.0']);
// with the default
t.same(parseEndpoint('tcp://1.2.3.4'), [3000, '1.2.3.4']);
t.end();
});
void test('parses UNIX domain socket URI', (t) => {
t.same(parseEndpoint('unix:/foo/bar.sock'), ['/foo/bar.sock']);
t.same(parseEndpoint('unix:///foo/bar.sock'), ['/foo/bar.sock']);
t.end();
});
void test('parses Windows named pipe URI', (t) => {
t.same(parseEndpoint('pipe:\\\\.\\pipe\\some-name'), [
'\\\\.\\pipe\\some-name',
]);
t.end();
});
void test('throws on invalid scheme (protocol)', (t) => {
t.throws(
() => parseEndpoint('foobar://blah'),
'Unknown --listen endpoint scheme (protocol): foobar:',
);
t.end();
});
void test('throws on invalid Windows named pipe', (t) => {
t.throws(
() => parseEndpoint('pipe:lolsickbro'),
'Invalid Windows named pipe endpoint: pipe:lolsickbro',
);
t.throws(
() => parseEndpoint('pipe://./pipe/lol'),
'Invalid Windows named pipe endpoint: pipe://./pipe/lol',
);
t.end();
});
void test('throws on invalid UNIX domain socket', (t) => {
t.throws(
() => parseEndpoint('unix:'),
'Invalid UNIX domain socket endpoint: unix:',
);
t.end();
});
================================================
FILE: test/tsconfig.json
================================================
{
"extends": "@vercel/style-guide/typescript",
"compilerOptions": {
"target": "ES2021",
"module": "CommonJS",
"moduleResolution": "Node16",
"esModuleInterop": true,
"noEmit": true
}
}
gitextract_h06eczrt/
├── .github/
│ └── workflows/
│ └── ci.yaml
├── .gitignore
├── .npmrc
├── .yarnrc
├── LICENSE
├── errors/
│ ├── invalid-entry.md
│ ├── invalid-package-json.md
│ ├── invalid-port-socket.md
│ ├── invalid-server-port.md
│ ├── invalid-socket.md
│ ├── no-export.md
│ ├── path-missing.md
│ └── path-not-existent.md
├── examples/
│ ├── external-api-call/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── json-body-parsing/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── socket.io-chat-app/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── package.json
│ │ └── websocket-server.js
│ ├── urlencoded-body-parsing/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── with-graphql-request/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ └── with-https/
│ ├── README.md
│ ├── index.js
│ └── package.json
├── lerna.json
├── package.json
├── packages/
│ └── micro/
│ ├── .eslintrc.js
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── bin/
│ │ │ └── micro.ts
│ │ └── lib/
│ │ ├── error.ts
│ │ ├── handler.ts
│ │ ├── index.ts
│ │ └── parse-endpoint.ts
│ ├── tsconfig.json
│ └── types/
│ └── src/
│ ├── bin/
│ │ └── micro.d.ts
│ └── lib/
│ ├── error.d.ts
│ ├── handler.d.ts
│ ├── index.d.ts
│ └── parse-endpoint.d.ts
└── test/
├── .eslintrc.js
├── package.json
├── suite/
│ ├── handler.ts
│ ├── index.ts
│ └── parse-endpoint.ts
└── tsconfig.json
SYMBOL INDEX (30 symbols across 8 files)
FILE: examples/with-https/index.js
constant PORT (line 6) | const PORT = process.env.PORT || 3443;
FILE: packages/micro/src/bin/micro.ts
function registerShutdown (line 130) | function registerShutdown(fn: () => void) {
function startEndpoint (line 145) | function startEndpoint(module: RequestHandler, endpoint: string) {
function start (line 173) | async function start() {
function hasMain (line 192) | function hasMain(packageJson: unknown): packageJson is { main: string } {
function isNodeError (line 200) | function isNodeError(
function isAddressInfo (line 206) | function isAddressInfo(obj: unknown): obj is AddressInfo {
FILE: packages/micro/src/lib/error.ts
function logError (line 3) | function logError(message: string, errorCode: string) {
FILE: packages/micro/src/lib/handler.ts
function isErrorObject (line 26) | function isErrorObject(error: unknown): error is Error {
FILE: packages/micro/src/lib/index.ts
function isStream (line 11) | function isStream(stream: unknown): stream is Stream {
function readable (line 20) | function readable(stream: unknown): stream is Readable {
type RequestHandler (line 28) | type RequestHandler = (
type Serve (line 33) | type Serve = (fn: RequestHandler) => RequestListener;
class HttpError (line 37) | class HttpError extends Error {
method constructor (line 38) | constructor(message: string) {
function isError (line 47) | function isError(error: unknown): error is Error | HttpError {
type BufferInfo (line 168) | interface BufferInfo {
function isRawBodyError (line 173) | function isRawBodyError(error: unknown): error is RawBodyError {
FILE: packages/micro/src/lib/parse-endpoint.ts
function parseEndpoint (line 1) | function parseEndpoint(endpoint: string) {
FILE: packages/micro/types/src/lib/index.d.ts
type RequestHandler (line 4) | type RequestHandler = (req: IncomingMessage, res: ServerResponse) => unk...
type Serve (line 5) | type Serve = (fn: RequestHandler) => RequestListener;
class HttpError (line 7) | class HttpError extends Error {
type BufferInfo (line 16) | interface BufferInfo {
FILE: test/suite/index.ts
function startServer (line 19) | function startServer(handler: RequestHandler): Promise<[string, () => vo...
function sleep (line 37) | function sleep(ms: number) {
type Payload (line 364) | interface Payload {
type Payload (line 392) | interface Payload {
type Payload (line 448) | interface Payload {
type Payload (line 483) | interface Payload {
type Payload (line 530) | interface Payload {
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (68K chars).
[
{
"path": ".github/workflows/ci.yaml",
"chars": 440,
"preview": "name: CI\non:\n - push\n - pull_request\njobs:\n test:\n name: Node.js ${{ matrix.node-version }}\n runs-on: ubuntu-la"
},
{
"path": ".gitignore",
"chars": 112,
"preview": "# dependencies\nnode_modules\n\n# logs\nnpm-debug.log\nyarn-error.log\n\n# coverage\ncoverage\n.nyc_output\n\n# build\ndist/"
},
{
"path": ".npmrc",
"chars": 48,
"preview": "save-exact = true\nstrict-peer-dependencies=false"
},
{
"path": ".yarnrc",
"chars": 15,
"preview": "save-prefix \"\"\n"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2023 Vercel, Inc.\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "errors/invalid-entry.md",
"chars": 584,
"preview": "# Invalid Entry File\n\n#### Why This Error Occurred\n\nWhen the `micro` command was ran, you passed a path to a file or dir"
},
{
"path": "errors/invalid-package-json.md",
"chars": 411,
"preview": "# Invalid `package.json` File\n\n#### Why This Error Occurred\n\nThe content of the `package.json` file that's located near "
},
{
"path": "errors/invalid-port-socket.md",
"chars": 339,
"preview": "# Port and socket provided\n\n#### Why This Error Occurred\n\nWhen the `micro` command was ran, you passed both a port and a"
},
{
"path": "errors/invalid-server-port.md",
"chars": 367,
"preview": "# Invalid Server Port\n\n#### Why This Error Occurred\n\nWhen the `micro` command was ran, you supplied the port flag althou"
},
{
"path": "errors/invalid-socket.md",
"chars": 222,
"preview": "# Invalid socket\n\n#### Why This Error Occurred\n\nWhen the `micro` command was ran, you passed `-s` or `--socket` without "
},
{
"path": "errors/no-export.md",
"chars": 517,
"preview": "# No Export\n\n#### Why This Error Occurred\n\nWhen `micro` tried to ran your microservice, it noticed that your code didn't"
},
{
"path": "errors/path-missing.md",
"chars": 521,
"preview": "# Path Missing\n\n#### Why This Error Occurred\n\nWhen running the `micro` command, you need to pass a path to a file or dir"
},
{
"path": "errors/path-not-existent.md",
"chars": 371,
"preview": "# Path Not Existent\n\n#### Why This Error Occurred\n\nWhen the `micro` command ran, you passed it a path to a file or direc"
},
{
"path": "examples/external-api-call/README.md",
"chars": 428,
"preview": "# External API call example\n\n## How to use\n\nDownload the example [or clone the repo](https://github.com/vercel/micro):\n\n"
},
{
"path": "examples/external-api-call/index.js",
"chars": 182,
"preview": "const fetch = require('node-fetch');\n\nmodule.exports = async () => {\n\tconst response = await fetch('https://api.example."
},
{
"path": "examples/external-api-call/package.json",
"chars": 230,
"preview": "{\n \"name\": \"external-api-call\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"micro\"\n },\n "
},
{
"path": "examples/json-body-parsing/README.md",
"chars": 433,
"preview": "# Parse JSON body example\n\n## How to use\n\nDownload the example [or clone the repo](https://github.com/vercel/micro):\n\n``"
},
{
"path": "examples/json-body-parsing/index.js",
"chars": 160,
"preview": "const {json} = require('micro');\n\nmodule.exports = async req => {\n\tconst data = await json(req);\n\tconsole.log(data);\n\n\tr"
},
{
"path": "examples/json-body-parsing/package.json",
"chars": 202,
"preview": "{\n \"name\": \"json-body-parsing\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"micro\"\n },\n "
},
{
"path": "examples/socket.io-chat-app/README.md",
"chars": 431,
"preview": "# Chat app with socket.io\n\n## How to use\n\nDownload the example [or clone the repo](https://github.com/vercel/micro):\n\n``"
},
{
"path": "examples/socket.io-chat-app/index.html",
"chars": 1293,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Socket.IO Chat Example</title>\n <link rel=\"st"
},
{
"path": "examples/socket.io-chat-app/index.js",
"chars": 484,
"preview": "const micro = require('micro');\nconst fs = require('fs');\nconst path = require('path');\n\nconst document = path.join(__di"
},
{
"path": "examples/socket.io-chat-app/package.json",
"chars": 231,
"preview": "{\n \"name\": \"chatnow\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"node .\"\n },\n \"author\":"
},
{
"path": "examples/socket.io-chat-app/websocket-server.js",
"chars": 310,
"preview": "module.exports = function startServer(io) {\n\tio.on('connection', socket => {\n\t\tconsole.log('a user connected');\n\n\t\tsocke"
},
{
"path": "examples/urlencoded-body-parsing/README.md",
"chars": 473,
"preview": "# Parse JSON body example\n\n## How to use\n\nDownload the example [or clone the repo](https://github.com/vercel/micro):\n\n``"
},
{
"path": "examples/urlencoded-body-parsing/index.js",
"chars": 177,
"preview": "const parse = require('urlencoded-body-parser');\n\nmodule.exports = async req => {\n\tconst data = await parse(req);\n\tconso"
},
{
"path": "examples/urlencoded-body-parsing/package.json",
"chars": 248,
"preview": "{\n \"name\": \"urlencoded-body-parsing\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"micro\"\n "
},
{
"path": "examples/with-graphql-request/README.md",
"chars": 626,
"preview": "# GraphQL Request example\n\n## How to use\n\nDownload the example [or clone the repo](https://github.com/vercel/micro):\n\n``"
},
{
"path": "examples/with-graphql-request/index.js",
"chars": 440,
"preview": "const {request} = require('graphql-request');\nconst endpoint = 'https://api.graph.cool/simple/v1/movies';\n\n// Prepare si"
},
{
"path": "examples/with-graphql-request/package.json",
"chars": 222,
"preview": "{\n \"name\": \"with-graphql-request\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"micro\"\n },"
},
{
"path": "examples/with-https/README.md",
"chars": 407,
"preview": "# Micro app with HTTPS\n\n## How to use\n\nDownload the example [or clone the repo](https://github.com/vercel/micro):\n\n```ba"
},
{
"path": "examples/with-https/index.js",
"chars": 499,
"preview": "const https = require('https');\nconst {run, send} = require('micro');\n\nconst {key, cert, passphrase} = require('openssl-"
},
{
"path": "examples/with-https/package.json",
"chars": 229,
"preview": "{\n \"name\": \"with-https\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"license\": \"MIT\",\n \"scripts\": {\n \"start\": \"n"
},
{
"path": "lerna.json",
"chars": 339,
"preview": "{\n \"npmClient\": \"yarn\",\n \"useWorkspaces\": true,\n \"packages\": [\n \"packages/*\"\n ],\n \"command\": {\n \"version\": {\n"
},
{
"path": "package.json",
"chars": 398,
"preview": "{\n \"private\": true,\n \"workspaces\": [\n \"packages/*\",\n \"test\"\n ],\n \"scripts\": {\n \"prepublish\": \"lerna run pre"
},
{
"path": "packages/micro/.eslintrc.js",
"chars": 305,
"preview": "module.exports = {\n root: true,\n extends: [\n require.resolve('@vercel/style-guide/eslint/node'),\n require.resolv"
},
{
"path": "packages/micro/README.md",
"chars": 12336,
"preview": "# Micro — Asynchronous HTTP microservices\n\n## Features\n\n- **Easy**: Designed for usage with `async` and `await`\n- **Fast"
},
{
"path": "packages/micro/package.json",
"chars": 1019,
"preview": "{\n \"name\": \"micro\",\n \"version\": \"10.0.1\",\n \"description\": \"Asynchronous HTTP microservices\",\n \"license\": \"MIT\",\n \"m"
},
{
"path": "packages/micro/src/bin/micro.ts",
"chars": 5139,
"preview": "#!/usr/bin/env node\n/* eslint-disable eslint-comments/disable-enable-pair */\n/* eslint-disable no-console */\n\n// Native\n"
},
{
"path": "packages/micro/src/lib/error.ts",
"chars": 260,
"preview": "// eslint-disable-next-line eslint-comments/disable-enable-pair\n/* eslint-disable no-console */\nexport function logError"
},
{
"path": "packages/micro/src/lib/handler.ts",
"chars": 691,
"preview": "// Utilities\nimport { logError } from './error';\n\nexport const handle = async (file: string) => {\n let mod: unknown;\n\n "
},
{
"path": "packages/micro/src/lib/index.ts",
"chars": 5532,
"preview": "// Native\nimport { Stream, Readable } from 'stream';\n// Packages\nimport contentType from 'content-type';\nimport getRawBo"
},
{
"path": "packages/micro/src/lib/parse-endpoint.ts",
"chars": 771,
"preview": "export function parseEndpoint(endpoint: string) {\n const url = new URL(endpoint);\n\n switch (url.protocol) {\n case '"
},
{
"path": "packages/micro/tsconfig.json",
"chars": 379,
"preview": "{\n \"extends\": \"@vercel/style-guide/typescript\",\n \"compilerOptions\": {\n \"target\": \"ES2021\",\n \"module\": \"CommonJS\""
},
{
"path": "packages/micro/types/src/bin/micro.d.ts",
"chars": 66,
"preview": "#!/usr/bin/env node\nexport {};\n//# sourceMappingURL=micro.d.ts.map"
},
{
"path": "packages/micro/types/src/lib/error.d.ts",
"chars": 111,
"preview": "export declare function logError(message: string, errorCode: string): void;\n//# sourceMappingURL=error.d.ts.map"
},
{
"path": "packages/micro/types/src/lib/handler.d.ts",
"chars": 103,
"preview": "export declare const handle: (file: string) => Promise<Function>;\n//# sourceMappingURL=handler.d.ts.map"
},
{
"path": "packages/micro/types/src/lib/index.d.ts",
"chars": 1321,
"preview": "/// <reference types=\"node\" />\n/// <reference types=\"node\" />\nimport type { IncomingMessage, ServerResponse, RequestList"
},
{
"path": "packages/micro/types/src/lib/parse-endpoint.d.ts",
"chars": 111,
"preview": "export declare function parseEndpoint(endpoint: string): string[];\n//# sourceMappingURL=parse-endpoint.d.ts.map"
},
{
"path": "test/.eslintrc.js",
"chars": 262,
"preview": "module.exports = {\n root: true,\n extends: [\n require.resolve('@vercel/style-guide/eslint/node'),\n require.resolv"
},
{
"path": "test/package.json",
"chars": 656,
"preview": "{\n \"name\": \"test\",\n \"version\": \"1.0.0\",\n \"engines\": {\n \"node\": \">= 16.0.0\"\n },\n \"scripts\": {\n \"eslint-check\":"
},
{
"path": "test/suite/handler.ts",
"chars": 1878,
"preview": "import { test } from 'tap';\nimport { handle } from 'micro/src/lib/handler';\nimport { stub } from 'sinon';\n\nvoid test('ha"
},
{
"path": "test/suite/index.ts",
"chars": 16007,
"preview": "import http from 'http';\nimport Stream from 'stream';\nimport { Socket } from 'net';\nimport { stub } from 'sinon';\nimport"
},
{
"path": "test/suite/parse-endpoint.ts",
"chars": 1453,
"preview": "import { test } from 'tap';\nimport { parseEndpoint } from 'micro/src/lib/parse-endpoint';\n\nvoid test('parses TCP URI', ("
},
{
"path": "test/tsconfig.json",
"chars": 210,
"preview": "{\n \"extends\": \"@vercel/style-guide/typescript\",\n \"compilerOptions\": {\n \"target\": \"ES2021\",\n \"module\": \"CommonJS\""
}
]
About this extraction
This page contains the full source code of the vercel/micro GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (60.6 KB), approximately 17.8k tokens, and a symbol index with 30 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.