Skip to content

Commit

Permalink
Making HTTP server optional, breaking changes to config type.
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinTail committed Oct 10, 2024
1 parent 426db7a commit 329c615
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 124 deletions.
31 changes: 13 additions & 18 deletions example/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,21 @@ import { readFile } from "node:fs/promises";
import createHttpError from "http-errors";

export const config = createConfig({
server: {
listen: 8090,
upload: {
limits: { fileSize: 51200 },
limitError: createHttpError(413, "The file is too large"), // affects uploadAvatarEndpoint
},
compression: true, // affects sendAvatarEndpoint
beforeRouting: async ({ app }) => {
// third-party middlewares serving their own routes or establishing their own routing besides the API
const documentation = yaml.parse(
await readFile("example/example.documentation.yaml", "utf-8"),
);
app.use("/docs", ui.serve, ui.setup(documentation));
},
http: { listen: 8090 },
upload: {
limits: { fileSize: 51200 },
limitError: createHttpError(413, "The file is too large"), // affects uploadAvatarEndpoint
},
cors: true,
logger: {
level: "debug",
color: true,
compression: true, // affects sendAvatarEndpoint
beforeRouting: async ({ app }) => {
// third-party middlewares serving their own routes or establishing their own routing besides the API
const documentation = yaml.parse(
await readFile("example/example.documentation.yaml", "utf-8"),
);
app.use("/docs", ui.serve, ui.setup(documentation));
},
cors: true,
logger: { level: "debug", color: true },
tags: {
users: "Everything about the users",
files: "Everything about the files processing",
Expand Down
70 changes: 35 additions & 35 deletions src/config-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,50 +141,50 @@ type BeforeRouting = (params: {

export interface ServerConfig<TAG extends string = string>
extends CommonConfig<TAG> {
/** @desc Server configuration. */
server: {
/** @desc HTTP server configuration. */
http?: {
/** @desc Port, UNIX socket or custom options. */
listen: number | string | ListenOptions;
/**
* @desc Custom JSON parser.
* @default express.json()
* @link https://expressjs.com/en/4x/api.html#express.json
* */
jsonParser?: RequestHandler;
/**
* @desc Enable or configure uploads handling.
* @default undefined
* @requires express-fileupload
* */
upload?: boolean | UploadOptions;
/**
* @desc Enable or configure response compression.
* @default undefined
* @requires compression
*/
compression?: boolean | CompressionOptions;
/**
* @desc Custom raw parser (assigns Buffer to request body)
* @default express.raw()
* @link https://expressjs.com/en/4x/api.html#express.raw
* */
rawParser?: RequestHandler;
/**
* @desc A code to execute before processing the Routing of your API (and before parsing).
* @desc This can be a good place for express middlewares establishing their own routes.
* @desc It can help to avoid making a DIY solution based on the attachRouting() approach.
* @default undefined
* @example ({ app }) => { app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); }
* */
beforeRouting?: BeforeRouting;
};
/** @desc Enables HTTPS server as well. */
/** @desc HTTPS server configuration. */
https?: {
/** @desc At least "cert" and "key" options required. */
options: ServerOptions;
/** @desc Port, UNIX socket or custom options. */
listen: number | string | ListenOptions;
};
/**
* @desc Custom JSON parser.
* @default express.json()
* @link https://expressjs.com/en/4x/api.html#express.json
* */
jsonParser?: RequestHandler;
/**
* @desc Enable or configure uploads handling.
* @default undefined
* @requires express-fileupload
* */
upload?: boolean | UploadOptions;
/**
* @desc Enable or configure response compression.
* @default undefined
* @requires compression
*/
compression?: boolean | CompressionOptions;
/**
* @desc Custom raw parser (assigns Buffer to request body)
* @default express.raw()
* @link https://expressjs.com/en/4x/api.html#express.raw
* */
rawParser?: RequestHandler;
/**
* @desc A code to execute before processing the Routing of your API (and before parsing).
* @desc This can be a good place for express middlewares establishing their own routes.
* @desc It can help to avoid making a DIY solution based on the attachRouting() approach.
* @default undefined
* @example ({ app }) => { app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); }
* */
beforeRouting?: BeforeRouting;
/**
* @desc Rejects new connections and attempts to finish ongoing ones in the specified time before exit.
* @default undefined
Expand Down
2 changes: 1 addition & 1 deletion src/server-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const createUploadParsers = async ({
}): Promise<RequestHandler[]> => {
const uploader = await loadPeer<typeof fileUpload>("express-fileupload");
const { limitError, beforeUpload, ...options } = {
...(typeof config.server.upload === "object" && config.server.upload),
...(typeof config.upload === "object" && config.upload),
};
const parsers: RequestHandler[] = [];
parsers.push(async (request, response, next) => {
Expand Down
26 changes: 13 additions & 13 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import express from "express";
import type compression from "compression";
import http from "node:http";
import https from "node:https";
import { ListenOptions } from "node:net";
import { reject, isNil } from "ramda";
import { BuiltinLogger } from "./builtin-logger";
import { AppConfig, CommonConfig, ServerConfig } from "./config-type";
import { isLoggerInstance } from "./logger-helpers";
Expand Down Expand Up @@ -64,27 +66,25 @@ export const createServer = async (config: ServerConfig, routing: Routing) => {
} = makeCommonEntities(config);
const app = express().disable("x-powered-by").use(loggingMiddleware);

if (config.server.compression) {
if (config.compression) {
const compressor = await loadPeer<typeof compression>("compression");
app.use(
compressor(
typeof config.server.compression === "object"
? config.server.compression
: undefined,
typeof config.compression === "object" ? config.compression : undefined,
),
);
}

const parsers: Parsers = {
json: [config.server.jsonParser || express.json()],
raw: [config.server.rawParser || express.raw(), moveRaw],
upload: config.server.upload
json: [config.jsonParser || express.json()],
raw: [config.rawParser || express.raw(), moveRaw],
upload: config.upload
? await createUploadParsers({ config, getChildLogger })
: [],
};

if (config.server.beforeRouting) {
await config.server.beforeRouting({
if (config.beforeRouting) {
await config.beforeRouting({
app,
logger: rootLogger,
getChildLogger,
Expand All @@ -95,16 +95,16 @@ export const createServer = async (config: ServerConfig, routing: Routing) => {

const starter = <T extends http.Server | https.Server>(
server: T,
subject?: typeof config.server.listen,
subject?: number | string | ListenOptions,
) => server.listen(subject, () => rootLogger.info("Listening", subject)) as T;

const httpServer = http.createServer(app);
const httpServer = config.http && http.createServer(app);
const httpsServer =
config.https && https.createServer(config.https.options, app);

if (config.gracefulShutdown) {
installTerminationListener({
servers: [httpServer].concat(httpsServer || []),
servers: reject(isNil, [httpServer, httpsServer]),
logger: rootLogger,
options: config.gracefulShutdown === true ? {} : config.gracefulShutdown,
});
Expand All @@ -113,7 +113,7 @@ export const createServer = async (config: ServerConfig, routing: Routing) => {
return {
app,
logger: rootLogger,
httpServer: starter(httpServer, config.server.listen),
httpServer: httpServer && starter(httpServer, config.http?.listen),
httpsServer: httpsServer && starter(httpsServer, config.https?.listen),
};
};
28 changes: 13 additions & 15 deletions tests/system/system.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,15 @@ describe("App", async () => {
const server = (
await createServer(
{
server: {
listen: port,
compression: { threshold: 1 },
beforeRouting: ({ app, getChildLogger }) => {
depd("express")("Sample deprecation message");
app.use((req, {}, next) => {
const childLogger = getChildLogger(req);
assert("isChild" in childLogger && childLogger.isChild);
next();
});
},
http: { listen: port },
compression: { threshold: 1 },
beforeRouting: ({ app, getChildLogger }) => {
depd("express")("Sample deprecation message");
app.use((req, {}, next) => {
const childLogger = getChildLogger(req);
assert("isChild" in childLogger && childLogger.isChild);
next();
});
},
cors: false,
startupLogo: true,
Expand All @@ -157,16 +155,16 @@ describe("App", async () => {
routing,
)
).httpServer;
await vi.waitFor(() => assert(server.listening), { timeout: 1e4 });
await vi.waitFor(() => assert(server?.listening), { timeout: 1e4 });
expect(warnMethod).toHaveBeenCalledWith(
"DeprecationError (express): Sample deprecation message",
expect.any(Array), // stack
);

afterAll(async () => {
server.close();
server?.close();
// this approach works better than .close() callback
await vi.waitFor(() => assert(!server.listening), { timeout: 1e4 });
await vi.waitFor(() => assert(!server?.listening), { timeout: 1e4 });
vi.restoreAllMocks();
});

Expand Down Expand Up @@ -476,7 +474,7 @@ describe("App", async () => {
timeout: 1000,
});
await setTimeout(1500);
expect(server.listening).toBeFalsy();
expect(server?.listening).toBeFalsy();
expect(spy).toHaveBeenCalled();
});
});
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/documentation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("Documentation", () => {
const sampleConfig = createConfig({
cors: true,
logger: { level: "silent" },
server: { listen: givePort() },
http: { listen: givePort() },
});

describe("Basic cases", () => {
Expand Down
12 changes: 5 additions & 7 deletions tests/unit/server-helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,11 @@ describe("Server helpers", () => {
const beforeUploadMock = vi.fn();
const parsers = await createUploadParsers({
config: {
server: {
listen: 8090,
upload: {
limits: { fileSize: 1024 },
limitError: new Error("Too heavy"),
beforeUpload: beforeUploadMock,
},
http: { listen: 8090 },
upload: {
limits: { fileSize: 1024 },
limitError: new Error("Too heavy"),
beforeUpload: beforeUploadMock,
},
cors: false,
logger: { level: "silent" },
Expand Down
Loading

0 comments on commit 329c615

Please sign in to comment.