@peter-schweitzer/ezserver
Version:
BLAZINGLY FAST npm module for backend/REST-API development with 0! dependencies, inspired by express
237 lines (207 loc) • 7.83 kB
JavaScript
import { createServer } from 'node:http';
import { ERR, LOG, WRN } from '@peter-schweitzer/ez-utils';
import { curry_middleware, handle_middleware } from './middleware.js';
import { Params } from './Params.js';
import { add_endpoint_to_corresponding_lut, get_endpoint, get_endpoint_with_param, get_endpoint_with_wildcard } from './routing.js';
import { throw404 } from './utils.js';
export class App {
/** @type {Server} */
m_http_server;
//#region endpoints
/** @type {ResolverLUT} */
#endpoints = {};
/** @type {TreeNode<false>} */
#endpoints_with_params = {};
//#region with wildcard
/** @type {{[x in Methods]: ResolverTreeContainer}} */
#rest_endpoints_with_wildcard = {
GET: { depth: 0, root: {} },
HEAD: { depth: 0, root: {} },
POST: { depth: 0, root: {} },
PUT: { depth: 0, root: {} },
DELETE: { depth: 0, root: {} },
CONNECT: { depth: 0, root: {} },
OPTIONS: { depth: 0, root: {} },
TRACE: { depth: 0, root: {} },
PATCH: { depth: 0, root: {} },
};
/** @type {ResolverTreeContainer} */
#endpoints_with_wildcard = { depth: 0, root: {} };
//#endregion
//#endregion
/** @type {FalseOr<Middleware[]>} */
#middleware = false;
constructor() {
this.m_http_server = createServer(async (/**@type {EZIncomingMessage}*/ req, res) => {
//#region variables
/** @type {LUT<string>} */
const query = {};
/**@type {LUT<string> & {"*"?: string[]}}*/
const route = {};
//#endregion
//#region parsing out URL & query parameters
const url = req.url;
const uri_end_idx = url.indexOf('?');
if (uri_end_idx === -1) req.uri = decodeURIComponent(url);
else {
req.uri = decodeURIComponent(url.slice(0, uri_end_idx));
//#region parsing out query parameters
const query_string = decodeURIComponent(url.slice(uri_end_idx + 1));
if (query_string.length !== 0)
for (const kv of query_string.split('&')) {
const [key, value] = kv.split('=');
if (key.length === 0 || value === undefined || value.length === 0) continue;
else query[key] = value;
}
//#endregion
}
//#endregion
//#region "global" middleware
if (!(await handle_middleware(this.#middleware, req, res, query, route))) return;
//#endregion
//#region routing
const leaf =
get_endpoint(this.#endpoints, req) ||
get_endpoint_with_param(this.#endpoints_with_params, req, route) ||
get_endpoint_with_wildcard(this.#rest_endpoints_with_wildcard[req.method], req, route) ||
get_endpoint_with_wildcard(this.#endpoints_with_wildcard, req, route);
if (leaf === false) return throw404(req, res);
const { fn, middleware } = leaf;
if (await handle_middleware(middleware, req, res, query, route)) fn(req, res, new Params(query, route));
//#endregion
});
}
//#region functions
//#region node:http Server functions
/**
* @param {number|string} port port the server will listen on
* @returns {void}
*/
listen(port) {
this.m_http_server.listen(port, () => LOG(`server listening on port ${port}`));
}
/** @returns {Promise<boolean>} never rejects; false if the server isn't open when close() is called */
async close() {
return new Promise((resolve, _) => {
this.m_http_server.close(function (err = null) {
if (err === null) WRN('server closed'), resolve(true);
else ERR(err), WRN("'close()' called, but server is already closed"), resolve(false);
});
});
}
//#endregion
//#region rest endpoints
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
get(uri, fn) {
LOG('added get:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.GET, uri, fn, 'GET');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
head(uri, fn) {
LOG('added head:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.HEAD, uri, fn, 'HEAD');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
post(uri, fn) {
LOG('added post:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.POST, uri, fn, 'POST');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
put(uri, fn) {
LOG('added put:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.PUT, uri, fn, 'PUT');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
delete(uri, fn) {
LOG('added delete:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.DELETE, uri, fn, 'DELETE');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
connect(uri, fn) {
LOG('added connect:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.CONNECT, uri, fn, 'CONNECT');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
options(uri, fn) {
LOG('added options:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.OPTIONS, uri, fn, 'OPTIONS');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
trace(uri, fn) {
LOG('added trace:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.TRACE, uri, fn, 'TRACE');
return curry_middleware(leaf);
}
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
patch(uri, fn) {
LOG('added patch:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#rest_endpoints_with_wildcard.PATCH, uri, fn, 'PATCH');
return curry_middleware(leaf);
}
//#endregion
//#region non rest endpoints
/**
* @param {string} uri uri to resolve
* @param {ResFunction} fn function for resolve the request
* @returns {MiddlewareCurry}
*/
add(uri, fn) {
LOG('added:', uri);
const leaf = add_endpoint_to_corresponding_lut(this.#endpoints, this.#endpoints_with_params, this.#endpoints_with_wildcard, uri, fn);
return curry_middleware(leaf);
}
//#endregion
//#endregion
/**
* @param {Middleware} middleware
* @returns {this}
*/
use(middleware) {
if (this.#middleware === false) this.#middleware = [];
this.#middleware.push(middleware);
return this;
}
}