faf v3.0.0
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
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago