@egomobile/http-server
TypeScript icon, indicating that this package has built-in type declarations

0.67.0 • Public • Published

npm PRs Welcome

@egomobile/http-server

A very fast alternative HTTP server to Express, with simple routing and middleware support, that is compatible with Node.js 18 or later.

Table of contents

Install []

Execute the following command from your project folder, where your package.json file is stored:

npm install --save @egomobile/http-server

If you want to lookup types, also install the Node Types:

npm install --save-dev @types/node

Usage []

Quick example []

import createServer, { buffer, params, query } from "@egomobile/http-server";

async function main() {
  const app = createServer();

  // POST request for / route
  // that uses the middleware buffer(), which loads the
  // whole request body with a limit of 128 MB by default
  // and writes the data to 'body' prop of 'request' object
  // as Buffer
  app.post("/", [buffer()], async (request, response) => {
    const name: string = request.body!.toString("utf8");

    response.write("Hello: " + name);
    // no response.end() is required here
  });

  // parameters require a special path validator here
  // s. https://github.com/lukeed/regexparam
  // for more information about the string format
  app.get(params("/foo/:bar/baz"), async (request, response) => {
    response.write("BAR: " + request.params!.bar);
  });

  // parse query parameters from URL
  // and write them to 'query' prop of 'request' object
  app.get("/foo", [query()], async (request, response) => {
    // request.query => https://nodejs.org/api/url.html#class-urlsearchparams

    response.write(" BAR: " + request.query!.get("bar"));
    response.write(" BAZ: " + request.query!.get("baz"));
  });

  await app.listen();
  console.log(`Server now running on port ${app.port} ...`);
}

main().catch(console.error);

Middlewares []

To enhance the functionality of your handlers, you can setup global or route specific middlewares.

For more details, have a look at the wiki page.

Controllers []

The module provides tools, like decorators, functions and classes, that helps to setup routes and their behavior on a quite simple and high level.

Have a look at the wiki page for detailed information.

Error handling []

import createServer from "@egomobile/http-server";

async function main() {
  // ...

  // custom error handler
  app.setErrorHandler(async (error, request, response) => {
    const errorMessage = Buffer.from("SERVER ERROR: " + String(error), "utf8");

    if (!response.headersSend) {
      response.writeHead(400, {
        "Content-Length": String(errorMessage.length),
      });
    }

    response.write(errorMessage);
    response.end();
  });

  // custom 404 handler
  app.setNotFoundHandler(async (request, response) => {
    const notFoundMessage = Buffer.from(`${request.url} not found!`, "utf8");

    if (!response.headersSend) {
      response.writeHead(404, {
        "Content-Length": String(notFoundMessage.length),
      });
    }

    response.write(notFoundMessage);
    response.end();
  });

  app.get("/", async (request, response) => {
    throw new Error("Something went wrong!");
  });

  // ...
}

main().catch(console.error);

Pretty error pages []

A nice example is, to use Youch! by Poppinss.

It prints pretty error pages in the browser:

import createServer, { prettyErrors } from "@egomobile/http-server";
import youch from "youch";

async function main() {
  // ...

  app.setErrorHandler(async (error, request, response) => {
    const html = Buffer.from(await new youch(error, request).toHTML(), "utf8");

    if (!response.headersSent) {
      response.writeHead(500, {
        "Content-Type": "text/html; charset=UTF-8",
        "Content-Length": String(html.length),
      });
    }

    response.end(html);
  });

  app.get("/", async (request, response) => {
    throw new Error("Oops! Something went wrong!");
  });

  // ...
}

main().catch(console.error);

A possible result could be:

Testing []

With decorators @Describe() and @It(), you can write automatic (unit-)tests, realized by any framework you want.

This example shows, how to implement tests with SuperTest (if you want to see a more detailed description of this feature, you can visit the wiki page):

Controller []

import {
  Controller,
  ControllerBase,
  Describe,
  GET,
  IHttpRequest,
  IHttpResponse,
  It,
} from "@egomobile/http-server";

@Controller()
@Describe("My controller")
export default class MyController extends ControllerBase {
  @GET("/foo/:bar")
  @It(
    "should return '{{body}}' in body with status {{status}} when submitting parameter {{parameter:bar}}",
    {
      expectations: {
        body: "BUZZ",
        status: 202,
      },
      parameters: {
        bar: "buzz",
      },
    }
  )
  async index(request: IHttpRequest, response: IHttpResponse) {
    response.writeHead(202);
    response.write(request.params!.bar.toUpperCase());
  }
}

Initialization []

import assert from "assert";
import supertest from "supertest";
import { createServer } from "@egomobile/http-server";

const app = createServer();

// event, that is executed, if a test is requested
app.on("test", async (context) => {
  const {
    body,
    description,
    escapedRoute,
    expectations,
    group,
    headers,
    httpMethod,
    server,
  } = context;

  try {
    process.stdout.write(`Running test [${group}] '${description}' ... `);

    // prepare request ...
    // HTTP method ...
    let request = supertest(server)[httpMethod](escapedRoute);
    // request headers ...
    for (const [headerName, headerValue] of Object.entries(headers)) {
      request = request.set(headerName, headerValue);
    }

    // send it
    const response = await request.send(body);

    assert.strictEqual(response.statusCode, expectations.status);

    // maybe some more code checking headers and
    // body data from `expectations` ...

    process.stdout.write(`✅\n`);
  } catch (error) {
    process.stdout.write(`❌: ${error}\n`);
  }
});

// run tests
await app.test();

// alternative:
//
// if you set `EGO_RUN_SETUP` to a truthy value like `1`
// the server does not start listening, instead it simply
// runs `app.test()`
//
// await app.listen();

Benchmarks []

  Express fastify polka @egomobile/http-server
Express - 93% 39% 30% 🐌
fastify 107% - 43% 32% 🐢
polka 256% 238% - 76% 🐇
@egomobile/http-server 337% 🚀🚀🚀 314% 🚀🚀 132% 🚀 -

The following benchmarks were made with wrk on the following machine, running Node v16.13.2:

Machine:

  • MacBook Pro (16", 2021)
  • CPU: Apple M1 Max
  • Memory: 64 GB
  • OS: MacOS 12.1

Command: wrk -t8 -c100 -d30s http://localhost:3000/user/123

Express:
=============
Running 30s test @ http://localhost:3000/user/123
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.56ms  674.79us  14.59ms   90.47%
    Req/Sec     3.39k   224.41     5.11k    75.04%
  809164 requests in 30.03s, 118.84MB read
Requests/sec:  26947.30
Transfer/sec:      3.96MB


Fastify:
=============
Running 30s test @ http://localhost:3000/user/123
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.32ms    0.95ms  19.41ms   85.25%
    Req/Sec     3.64k   280.76     4.87k    76.38%
  869871 requests in 30.03s, 142.69MB read
Requests/sec:  28971.44
Transfer/sec:      4.75MB


Polka:
===========
Running 30s test @ http://localhost:3000/user/123
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.39ms  289.29us  13.20ms   91.15%
    Req/Sec     8.66k     1.26k   10.67k    59.55%
  2074873 requests in 30.10s, 259.22MB read
Requests/sec:  68930.81
Transfer/sec:      8.61MB


@egomobile/http-server:
============================
Running 30s test @ http://localhost:3000/user/123
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.05ms  220.64us  13.11ms   85.16%
    Req/Sec    11.44k     1.39k   18.48k    81.16%
  2737095 requests in 30.10s, 341.95MB read
Requests/sec:  90922.13
Transfer/sec:     11.36MB

Here is the test code, used recording the benchmarks.

Credits []

The module makes use of:

Documentation []

The API documentation can be found here.

See also []

Package Sidebar

Install

npm i @egomobile/http-server

Weekly Downloads

3

Version

0.67.0

License

LGPL-3.0

Unpacked Size

509 kB

Total Files

145

Last publish

Collaborators

  • ekmobile
  • egodux
  • mkloubertego
  • mkloubert
  • ekegodigital