3.0.0 • Published 4 years ago

faf v3.0.0

Weekly downloads
6
License
MIT
Repository
github
Last release
4 years ago

Fast as f**k http framework for building simple JSON APIs.

The idea behind faf is to provide a fetch-like API: A Request comes in, a Response goes out, but the other way around. Instead of calling response.json() you would do request.json().

Installation

$ npm install faf

// or
$ yarn add faf

Usage

import faf, { Response, HttpStatus } from "faf";

const app = faf();

app.post("/products", async (request) => {
  // You should validate this with your library of choice
  const body = await request.json();
  const product = await prisma.product.create(body);
  return Response.json(product, { status: HttpStatus.Created });
});

const { port } = await app.bind(4000);
console.log(`Listening on port ${port}`);

Middleware

faf does not support middleware like express does. Having said that, it actually uses a few common middleware under the hood.

const app = faf({
  logger: true, // uses `pino-http`
  cors: true, // uses `cors`
  helmet: true, // uses `helmet`
});

The options are the same as the ones you would use inside an express project, except for logger which can only be enabled or diabled.

Dynamic and Query Params

Faf normalizes both query and dynamic params into a single URLSearchParams instance. note: dynamic params override query string params.

// incoming url: /products/5?limit=10
app.get("/products/:id", (request) => {
  console.log(request.pathname); // /products/5
  console.log(request.search); // URLSearchParams { 'id' => '5', 'limit' => '10' }
});

API

Application

You can create an application in two ways:

import faf from "faf";

const app = faf();

// or

import { Application } from "faf";

const app = new Application();
class Application {
  on(method: HttpMethod, path: string, requestHandler: RequestHandler): this;

  get(path: string, requestHandler: RequestHandler): this;

  post(path: string, requestHandler: RequestHandler): this;

  put(path: string, requestHandler: RequestHandler): this;

  patch(path: string, requestHandler: RequestHandler): this;

  del(path: string, requestHandler: RequestHandler): this;

  head(path: string, requestHandler: RequestHandler): this;

  all(path: string, requestHandler: RequestHandler): this;

  /**
   * Start the server
   *
   * @param port string | number | undefined
   * @param host string | undefined
   * @returns Promise<AddressInfo>
   */
  bind(port?: string | number, host?: string): Promise<AddressInfo>;

  /**
   * Stop the server
   *
   * @returns Promise<void>
   */
  unbind(): Promise<void>;
}

Request

Request is not intended to be instantiated by you. faf creates one for you once per request (forgive the redundancy).

class Request {
  readonly headers: IncomingHttpHeaders;
  readonly method: HttpMethod;
  readonly pathname: string;
  readonly search: URLSearchParams;

  get body(): Readable;

  get bodyUsed(): boolean;

  /**
   * Decode response as Buffer
   *
   * @returns Promise<Buffer>
   */
  buffer(): Promise<Buffer>;

  /**
   * Decode response as ArrayBuffer
   *
   * @returns Promise<ArrayBuffer>
   */
  arrayBuffer(): Promise<ArrayBuffer>;

  /**
   * Decode response as text
   *
   * @returns Promise<string>
   */
  text(): Promise<string>;

  /**
   * Decode response as JSON
   *
   * @returns Promise<unknown>
   */
  json(): Promise<unknown>;

  /**
   * Decode response as URLSearchParams
   *
   * @returns Promise<URLSearchParams>
   */
  form(): Promise<URLSearchParams>;
}

Response

Response is intended to be instantiated using its static methods, dependeing on what you want to respond with.

class Response {
  /**
   * Create a buffer response
   *
   * @param data Buffer
   * @param init ResponseInit
   * @returns Response
   */
  static buffer(data: Buffer, init?: ResponseInit): Response;

  /**
   * Create a stream response
   *
   * @param readable Stream
   * @param init ResponseInit
   * @returns Response
   */
  static stream(readable: Readable, init?: ResponseInit): Response;

  /**
   * Create a json response
   *
   * @param data unknown
   * @param init ResponseInit
   * @returns Response
   */
  static json(data: unknown, init?: ResponseInit): Response;

  /**
   * Create a text response
   *
   * @param str string
   * @param init ResponseInit
   * @returns Response
   */
  static text(str: string, init?: ResponseInit): Response;

  /**
   * Create an empty response with the specified status code
   *
   * @param httpStatus HttpStatus
   * @param init ResponseInit
   * @returns Response
   */
  static status(
    httpStatus: HttpStatus,
    init?: Omit<ResponseInit, "status">,
  ): Response;
}

raise

raise is used when you want to terminate execution. It throws a special HttpError which faf will intercept and reply accordingly. raise takes an HttpStatus as its first argument, and an optional second argument which can be a JSON serializable object.

import { raise, HttpStatus } from "faf";

app.get("/products/:id", () => {
  raise(HttpStatus.NotFound);
});
function raise(httpStatus: HttpStatus, body?: unknown): never;

HttpStatus

HttpStatus is a type-safe map of all available status codes.

import { HttpStatus } from "faf";

HttpStatus.OK; // => 200
HttpStatus.NotFound; // => 404

// And so on...

Benchmarks

Command

bombardier -c 126 -n 1000000 http://localhost:4000/example

Code

import { Application, Response } from "faf";

const app = new Application();

app.get("/example", () => Response.text("Hello world"));

app.bind(4000);

Output

Statistics        Avg      Stdev        Max
  Reqs/sec     29743.99    3133.09   33295.51
  Latency        4.23ms   582.40us    50.52ms
  HTTP codes:
    1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:     6.50MB/s

faf is pretty minimalist, so it is fast. Here's the ouput for the express equivalent.

Statistics        Avg      Stdev        Max
  Reqs/sec      8880.65     839.30    9964.55
  Latency       14.19ms     0.93ms    69.20ms
  HTTP codes:
    1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:     2.60MB/s
2.3.0

4 years ago

2.2.0

4 years ago

2.1.1

4 years ago

2.0.2

4 years ago

2.5.0

4 years ago

2.4.1

4 years ago

2.4.0

4 years ago

2.4.2

4 years ago

2.1.0

4 years ago

2.0.1

4 years ago

2.0.0

4 years ago

3.0.1

4 years ago

3.0.0

4 years ago

1.6.1

4 years ago

1.6.0

4 years ago

1.5.0

4 years ago

1.2.0

4 years ago

1.3.3

4 years ago

1.3.2

4 years ago

1.4.0

4 years ago

1.3.0

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.0

4 years ago

0.1.0

4 years ago

0.0.0

4 years ago

0.1.1

4 years ago

0.0.1

4 years ago