Skip to content
This repository has been archived by the owner on May 22, 2024. It is now read-only.
Marcel Kloubert edited this page Mar 14, 2023 · 2 revisions

@egomobile/http-server

Very fast alternative HTTP server to Express, with simple routing and middleware support and which ist compatible with Node.js 16 or later.

Install

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

npm i @egomobile/http-server

Getting started

The following TypeScript example shows how to implement a tiny and fast "Hello, world!" API:

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

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

  app.get("/", async (request, response) => {
    response.write("Hello, world!");
  });

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

main().catch(console.error);

Middlewares

There are build-in middlewares for the most common tasks and cases.

One could be, to handle input data as JSON:

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

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

  // handle input data as UTF-8 JSON string,
  // parse it and write it to 'request.body'
  //               πŸ‘‡
  app.post("/", [json()], async (request, response) => {
    response.write("Your input: " + JSON.stringify(request.body));
  });

  await app.listen();
}

main().catch(console.error);

Detailed information provides ths following subpage.

Controllers

The module provides an easy way to define routes and their behavior with the help of controller classes.

The first, you have to do, is to define a sub directory inside your project root folder, lets say /controllers, which contains all controllers and their classes.

Inside that directory, you can start with an index.ts:

.
β”œβ”€β”€ controllers
β”‚   β”œβ”€β”€ index.ts
└── index.ts

Use the following skeleton to setup a simple controller:

import {
  Controller,
  ControllerBase,
  DELETE,
  GET,
  IHttpRequest,
  IHttpResponse,
  PUT,
  schema,
} from "@egomobile/http-server";

const mySchema = schema.object({
  email: schema.string().strict().trim().email().required(),
  name: schema.string().strict().trim().min(1).optional(),
});

/**
 * /controllers/index.ts
 *
 * Base path: '/'
 */
@Controller() // mark the class as controller
export default class MyController extends ControllerBase {
  // full path: /
  @GET()
  async index(request: IHttpRequest, response: IHttpResponse) {
    response.write("Hello, world!");
  }

  // full path: /foo
  @PUT(mySchema)
  async foo(request: IHttpRequest, response: IHttpResponse) {
    response.write(
      `Hello, ${request.body!.name} (${request.body!.email}), from foo!`
    );
  }

  // full path: /foo/:bar
  @DELETE("/foo/:bar")
  async deleteExample(request: IHttpRequest, response: IHttpResponse) {
    response.write("Bye, " + request.params!.bar);
  }
}

In your root /index.ts, you can initialize by calling the controllers() method of your server instance:

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

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

  // scan sub folder for classes
  app.controllers(__dirname + "/controllers");

  await app.listen();
}

main().catch(console.error);

This page describes anything in more details.

GraphQL

Using GraphQL with this module is quite simple.

import createServer, {
  HttpPathValidator,
  HttpRequestHandler,
} from "@egomobile/http-server";
import schema from "./graphql";
import { graphqlHTTP } from "express-graphql";

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

  // add GraphQL middleware
  // with '/graphql' endpoint
  app.all(
    (request) => request.url!.startsWith("/graphql"),
    [
      graphqlHTTP({
        // [1] https://graphql.org/learn/schema/
        // [2] https://graphql.org/learn/queries/
        schema,
        graphiql: true, // use UI
      }) as any,
    ],
    async () => {
      /** this will never be called, but we need this there */
    }
  );

  // access UI via
  // http://localhost:8080/graphql
  await app.listen(8080);
}

main().catch(console.error);

For more information, you can have a look at here.

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:

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 BUZZ in code with status 202", {
    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.once("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();

Have a look at this wiki page to get more in touch.