0.67.0 • Published 2 months ago

@egomobile/http-server v0.67.0

Weekly downloads
-
License
LGPL-3.0
Repository
github
Last release
2 months ago

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

 Expressfastifypolka@egomobile/http-server
Express-93%39%30% 🐌
fastify107%-43%32% 🐢
polka256%238%-76% 🐇
@egomobile/http-server337% 🚀🚀🚀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

0.67.0

2 months ago

0.68.0-alpha.390

2 months ago

0.67.0-alpha.388

3 months ago

0.66.0

3 months ago

0.65.4

4 months ago

0.66.0-alpha.386

5 months ago

0.65.2

5 months ago

0.65.1

5 months ago

0.65.3

5 months ago

0.65.0

5 months ago

0.64.0-alpha.379

9 months ago

0.65.0-alpha.382

5 months ago

0.64.2

5 months ago

0.64.1

5 months ago

0.64.0

5 months ago

0.62.1

1 year ago

0.62.0

1 year ago

0.62.3

1 year ago

0.62.2

1 year ago

0.63.0

1 year ago

0.63.1

1 year ago

0.43.0

1 year ago

0.59.0

1 year ago

0.55.0

1 year ago

0.51.2

1 year ago

0.51.3

1 year ago

0.51.0

1 year ago

0.51.1

1 year ago

0.48.2

1 year ago

0.48.3

1 year ago

0.48.0

1 year ago

0.48.1

1 year ago

0.44.0

1 year ago

0.56.1

1 year ago

0.56.2

1 year ago

0.56.0

1 year ago

0.52.0

1 year ago

0.49.1

1 year ago

0.49.0

1 year ago

0.45.0

1 year ago

0.60.3

1 year ago

0.41.1

1 year ago

0.60.2

1 year ago

0.60.4

1 year ago

0.41.0

1 year ago

0.60.0

1 year ago

0.57.0

1 year ago

0.53.0

1 year ago

0.46.0

1 year ago

0.42.2

1 year ago

0.42.0

1 year ago

0.42.1

1 year ago

0.61.0

1 year ago

0.58.0

1 year ago

0.54.0

1 year ago

0.50.1

1 year ago

0.50.2

1 year ago

0.50.0

1 year ago

0.47.1

1 year ago

0.43.3

1 year ago

0.47.0

1 year ago

0.43.4

1 year ago

0.43.1

1 year ago

0.43.2

1 year ago

0.38.0

2 years ago

0.36.2

2 years ago

0.36.1

2 years ago

0.36.0

2 years ago

0.40.0

2 years ago

0.39.0

2 years ago

0.37.0

2 years ago

0.35.2

2 years ago

0.35.1

2 years ago

0.35.0

2 years ago

0.34.0

2 years ago

0.32.0

2 years ago

0.30.2

2 years ago

0.30.0

2 years ago

0.29.0

2 years ago

0.27.1

2 years ago

0.27.0

2 years ago

0.25.0

2 years ago

0.33.1

2 years ago

0.33.0

2 years ago

0.31.0

2 years ago

0.28.0

2 years ago

0.26.1

2 years ago

0.26.0

2 years ago

0.24.0

2 years ago

0.21.0

2 years ago

0.20.0

2 years ago

0.19.0

2 years ago

0.18.1

2 years ago

0.19.1

2 years ago

0.18.0

2 years ago

0.23.0

2 years ago

0.22.0

2 years ago

0.16.0

2 years ago

0.15.1

2 years ago

0.17.0

2 years ago

0.17.1

2 years ago

0.14.0

3 years ago

0.15.0

3 years ago

0.13.0

3 years ago

0.13.1

3 years ago

0.12.0

3 years ago

0.11.0

3 years ago

0.10.0

3 years ago

0.9.0

3 years ago

0.9.1

3 years ago

0.8.0

3 years ago

0.7.0

3 years ago

0.5.0

3 years ago

0.4.0

3 years ago

0.6.0

3 years ago

0.3.0

3 years ago

0.2.1

3 years ago

0.2.0

3 years ago

0.3.1

3 years ago

0.2.2

3 years ago

0.1.3

3 years ago

0.1.2

3 years ago

0.1.1

3 years ago