npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@amphibian/server

v2.2.1

Published

A bare bones node.js server that makes setup and maintaining a breeze

Downloads

103

Readme

@amphibian/server

build status coverage report

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);