Tools for building Node.js apps with Zeit's Micro
Micro defines a set of powerful primitives for building endpoints with Node.js. Micro extensions provides additional functions and helpers that extend Micro's core making it easy to weave together small applications into larger ones.
Note: we currently transpile using babel until Node.js 8 comes out then will deprecate transpiling and the need to reach into lib/
to reach things that are not exported from main
.
Micro extensions also extend Micro, which is very unopinionated, with one important principle:
State mutation and other side-effects must be isolated and explicit
import { configureRoutes } from 'micro-extensions/lib'
const routes = configureRoutes({
routes: [
{ method: 'get', pattern: '/foo', handler: fooHandler },
{ method: 'get', pattern: '/foo/fuzz', handler: fuzzHandler },
...notFoundRoutes
],
effects: { database },
middlewares: [ logRequests ]
})
const app = createApp(routes)
micro(app).listen(2222)
Everything in Micro extensions is made to work well together or separately as much as possible.
A flexible router for declaratively composing request handlers, effects, middleware and application configs into applications.
- Routers can be combined to create larger applications
- Easy to inject effects and apply middleware to all or a subset of routes
See router.
Request handlers accept a context object containing the familiar req
and res
as well as any applicable effects and/or application config. This avoids the common Node.js practice of mutating the req
object to maintian context and promotes explicit declaration of a request handler dependencies. See request handlers.
Side-effects are first class concepts that materialize in Micro extensions as helpers called "effects". Effects are injected into request handlers at runtime providing well defined interfaces over state mutations and other side-effects, e.g.:
const createFooHandler = async ({ req, res, effects: { apiClient } }) => {
return await apiClient.post('https://api.foo.com/foos', { bar: true })
}
Some common effects are provided out of the box:
- Logger
- Session
- API client
Effects have access to the request context and can also be used in middleware. See effects.
Middleware are functions that wrap response handlers. Middleware have access to a full response handler context object. See middleware.
Helpers for loading and validating application configurations. See application config.
The approach of Micro makes testing incredibly easy. Some helpers are provided to help reduce boilerplate. See testing.
A helper app is provided for generating documentation from code. See documentation.
Reusable sub applications can be easily created by exporting a call to configureRoutes
allowing you to package routes, effects, and middleware into a set of routes for reuse in larger applications which can supply the application configuration as well as additional effects and middleware. See plugins.
Routes are arrays of route declarations. Each route declares an HTTP method, a path pattern, and a handler to invoke for matching requests, e.g.:
const routes = [
{
method: 'get',
pattern: '/foo/:fooId',
handler: ({ params: { fooId } }) => ({ ok: true, fooId })
},
{ method: 'all', pattern: '/*', handler: () => { throw createError(404, 'Not Found') } }
]
See route declarations to learn about the structure of a route.
Configure a set of routes with effects, middleware and application config.
Arguments
routes
are an array of route definitionseffects
are a map of effect names to objectsmiddlewares
are an array of middleware functionsconfig
an application configuration objects
Returns
A flattened array of route definitions
Creates a Micro-compatible handler from an array of route definitions.
Arguments
routes
are an array of route definitions
Returns
A Micro-compatible request handler
Adds a path prefix to a set of routes.
Arguments
prefix
is string describing the path each route should be prefixed with.- Prefixes should not include parameters (TODO formalize this)
routes
are an array of route definitions
Returns
An array of route definitions
A route declaration describes a URL pattern and HTTP verb combination along with a handler for matching requests.
method
any HTTP verb or "all" to handle any verbpattern
a url pathname pattern- Named parameters are made available to request handlers via an object called
params
- Named parameters are made available to request handlers via an object called
handler
a request handler to invoke for matching requests
Users are encouraged to consider if desired behavior can be achieved with effects because they encourage explicit calls inside requests handler code rather than introducing the indirection that comes with middleware. With that said it's easy to define new middleware when you want to apply some behavior to one more more routes unobtrusively, e.g.:
const logMiddleware handler => ({ effects: { logger } }) => {
logger.info('Yay, someone invoked me!')
}
Some generally useful middleware is provided
- Request logger: provide default request logging for an app or set of routes
- Error page renderer: renders a React component statically (server-side) with any uncaught error object
Arguments
handler
request handler to be wrapped by middleware
Returns a request handler
Micro extensions embraces the concept of "rich request handlers" which can be found in Next.js. The only difference is that instead of the typical Node.js request handler that accepts a req
as its first argument and res
as its second argument it receives a single context object with req
and res
as top-level attributes.
Before:
const fooHandler = (req, res) => ({ ok: true })
After:
const richFooHandler = ({ req, res, query }) => ({
ok: true,
foo: query.foo
})
req
vanilla Node.js request objectres
vanilla Node.js response objectquery
object of key/value pairs parsed out of the URL query stringparams
object of key/value pairs parsed out of any named parameters in the requested URLpathname
the pathname of the requested URLconfig
application config object config objecteffects
object where the keys are the names of side-effects and the values are effects helpers
Adopting the convention of passing a context object into a handler counteracts the common approach across Node.js frameworks of mutating the request object as a way for different parts of the application to coordinate (e.g. express session middleware adds a session
attribute to req
and expects you to find it by convention in handlers). There's no great problem with this but it forces your application to depend on a "proprietary" req
object and ultimately leads to indirection that makes it harder to reason about. On the other hand, by accepting a context object, libraries (like the included router) can add to the context of the request without having to rely on reading/writing to the request object. E.g. the application config is made available to all requests as a top level key:
const configurableFooHandler = ({
req,
res,
config: { apiUrl }
}) => ({
apiUrl,
ok: true
})
Adds a beforeEach
and afterEach
to suppress all calls to console.error
during test run. Used to quiet Micro tests that invoke sendError
.
There's no specific documentation for plugins because to create one you simply export a call to configureRoutes
from a module.
Some plugins ship with Micro extensions:
- Not found
- OpenAPI docs