@amphibian/server
v2.2.1
Published
A bare bones node.js server that makes setup and maintaining a breeze
Downloads
103
Readme
@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 call next
, 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 a RegExp
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
Provide path
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 of RegExp
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 in createServer
.
options.port (number)
Sets the port the server will listen on.
Default: 4000
options.logging (boolean|'errors')
Enables or disables logging. Set to String
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 a SIGTERM
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);