@amphibian/server
A bare bones node.js server based on Koa that makes setup and maintaining a breeze.
import createServer from '@amphibian/server';
const server = createServer();
async function helloWorldHandler(context, next) {
context.status = 200;
context.body = {
message: 'Hello World!',
something: await operation()
};
}
server.registerRouteHandler(helloWorldHandler, {
method: 'get',
path: '/hello-world'
});
Handlers
There are two methods for setting up handlers:registerHandler
and registerRouteHandler
. The first method, registerHandler
, does not bind to any specific route and method, while the second, registerRouteHandler
, does.You will have to call
await next()
yourself if the handler should be skipped.registerHandler
function normalHandler(context, next) {
if (context.method.toLowerCase() === 'get') {
context.body = {message: 'It works!'};
return;
}
await next();
}
server.registerHandler(normalHandler);
registerRouteHandler
Takes three arguments: name
, handler
and options
. name
can be omitted. options
takes two properties: method
and path
.registerRouteHandler
takes care of calling await next()
for you.function routeHandler(context) {
context.body = {message: 'It works!'}
}
server.registerRouteHandler(routeHandler, {
method: 'get',
path: '/hello'
});
Middleware
Creating middleware is as easy as it gets. Just callnext
, the second handler argument, to pass the request onto the next handler in the list.async function logTimeMiddleware(context, next) {
console.time('request')
await next();
console.timeEnd('request');
}
server.registerMiddleware(logTimeMiddleware);
Note that
registerMiddleware
is exactly the same function as registerHandler
– using it is syntactic sugar that logs “Registered middleware” instead of “Registered handler”.Advanced routing
For reference, the provided route (whether it's aRegExp
or a String
) is placed in context.routePath
. This might be useful for measuring the performance of each route.async function logHandler(context) {
console.log(context.routePath); // > /^\/message\/([^\/]+)$/
}
server.registerRouteHandler(logHandler, {
method: 'get',
path: /^\/message\/([^\/]+)$/
});
String with parameters
Providepath
as a String
containing parameters prefixed by :
.async function advancedStringHandler(context) {
const {yourMessage} = context.args;
context.status = 200;
context.body = {message: `Your message is: ${yourMessage}`};
}
server.registerRouteHandler(advancedStringHandler, {
method: 'get',
path: '/message/:yourMessage'
});
Path parameter matches are placed as property of the
context.args
Object
.RegExp
To take advantage ofRegExp
routes using the built in router
utility, simply send a regular expression instead of a String
in the path
key of the options
object:async function advancedRegExpHandler(context) {
const [message] = context.args;
context.status = 200;
context.body = {message: `Your message is: ${message}`};
}
server.registerRouteHandler(advancedRegExpHandler, {
method: 'get',
path: /^\/message\/([^\/]+)$/
});
Any
RegExp
matches are placed as an Array
in context.args
.Options object
An options object can be passed as an argument increateServer
.options.port (number)
Sets the port the server will listen on. Default:4000
options.logging (boolean|'errors')
Enables or disables logging. Set toString
errors
to only log errors.
Default: true
options.listen (boolean)
Useful when using@amphibian/server
in conjunction with socket.io
. Read more here.
Default: true
Error handling
@amphibian/server
will automatically handle errors by serving an error
object to the body
. The error object has two properties: code
and message
.{
"error": {
"code": "unknown_error",
"message": "Something went wrong"
}
}
error.code
will default to unknown_error
if no code
is set on the Error
instance. The response.status
will default to 500
unless the Error
instance has a status
property. You can safely throw errors from a handler:async function throwingHandler(context) {
const error = new Error('Something went wrong');
error.status = 400;
error.code = 'my_error_code';
throw error;
}
server.registerRouteHandler(throwingHandler, {
method: 'get',
path: '/error'
});
Navigating to
/error
will yield the following response.body
, with 400
as response.status
:{
"error": {
"code": "my_error_code",
"message": "Something went wrong"
}
}
Error boundaries
To do handle the errors before@amphibian/server
spits them out, set up an error boundary:async function errorBoundary(context, next) {
try {
await next();
} catch (error) {
if (error.code === 'fatal_error') {
console.log(error.stack);
throw new Error('camouflaged error');
}
throw error;
}
}
server.registerMiddleware(errorBoundary);
Ensure the
errorBoundary
is the first middleware/handler you register. You won't be able to catch errors from those that come before it.Prevent logging an error
To prevent@amphiban/server
from logging an error, give the error a log
property set to false
.const error = new Error('some_error');
error.log = false;
throw error;
Graceful shutdown
To avoid dropping active requests when receiving aSIGTERM
signal, you should implement a SIGTERM
signal handler on the node process
. This will allow the server to finish active requests before shutting down.The
@amphibian/server
server
Object
has a Function
close
that returns a Promise
:import createServer from '@amphibian/server';
const server = createServer();
process.on('SIGTERM', async () => {
await server.close();
process.exit();
});
New server requests that are received during shutdown will be refused as per net.server.close().
Usage with socket.io (as a koa callback)
import http from 'http';
import io from 'socket.io';
import createServer from '@amphibian/server';
const server = createServer({listen: false});
const httpServer = http.createServer(app.callback());
const socket = io(httpServer);
io.on('connection', /* ... */);
httpServer.listen(3000);