feat: initial backend service template
Some checks failed
/ checks (push) Successful in 35s
/ deploy (push) Failing after 2s
Some checks failed
/ checks (push) Successful in 35s
/ deploy (push) Failing after 2s
This commit is contained in:
commit
2828a630a6
28 changed files with 6437 additions and 0 deletions
25
src/lib/config.ts
Normal file
25
src/lib/config.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
PORT: z.coerce.number().int().positive().default(3000),
|
||||
SERVICE_NAME: z.string().min(1, "SERVICE_NAME is required"),
|
||||
LOG_LEVEL: z
|
||||
.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"])
|
||||
.default("info"),
|
||||
GIT_SHA: z.string().default("dev"),
|
||||
DATABASE_URL: z.string().url().optional(),
|
||||
REDIS_URL: z.string().url().optional(),
|
||||
});
|
||||
|
||||
export type Config = z.infer<typeof schema>;
|
||||
|
||||
export function parseConfig(env: NodeJS.ProcessEnv): Config {
|
||||
const result = schema.safeParse(env);
|
||||
if (!result.success) {
|
||||
const problems = result.error.issues
|
||||
.map((i) => ` ${i.path.join(".")}: ${i.message}`)
|
||||
.join("\n");
|
||||
throw new Error(`Invalid environment:\n${problems}`);
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
24
src/lib/db.ts
Normal file
24
src/lib/db.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import pg from "pg";
|
||||
|
||||
export interface Db {
|
||||
query: pg.Pool["query"];
|
||||
ping(): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export function createDb(connectionString: string): Db {
|
||||
const pool = new pg.Pool({
|
||||
connectionString,
|
||||
max: 5,
|
||||
connectionTimeoutMillis: 2000,
|
||||
});
|
||||
// Idle-client errors must not crash the process.
|
||||
pool.on("error", () => {});
|
||||
return {
|
||||
query: pool.query.bind(pool) as pg.Pool["query"],
|
||||
async ping() {
|
||||
await pool.query("SELECT 1");
|
||||
},
|
||||
close: () => pool.end(),
|
||||
};
|
||||
}
|
||||
14
src/lib/logger.ts
Normal file
14
src/lib/logger.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import pino from "pino";
|
||||
import type { Config } from "./config.js";
|
||||
|
||||
// Logging contract: one JSON object per line on stdout with level, time,
|
||||
// msg, service, version. Level is emitted as a label, time as ISO-8601.
|
||||
export function createLogger(config: Config): pino.Logger {
|
||||
return pino({
|
||||
level: config.LOG_LEVEL,
|
||||
base: { service: config.SERVICE_NAME, version: config.GIT_SHA },
|
||||
messageKey: "msg",
|
||||
timestamp: pino.stdTimeFunctions.isoTime,
|
||||
formatters: { level: (label) => ({ level: label }) },
|
||||
});
|
||||
}
|
||||
26
src/lib/redis.ts
Normal file
26
src/lib/redis.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Redis } from "ioredis";
|
||||
|
||||
export interface Cache {
|
||||
incr(key: string): Promise<number>;
|
||||
ping(): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export function createCache(url: string): Cache {
|
||||
const client = new Redis(url, {
|
||||
maxRetriesPerRequest: 1,
|
||||
connectTimeout: 2000,
|
||||
enableOfflineQueue: false,
|
||||
lazyConnect: false,
|
||||
});
|
||||
client.on("error", () => {});
|
||||
return {
|
||||
incr: (key) => client.incr(key),
|
||||
async ping() {
|
||||
await client.ping();
|
||||
},
|
||||
async close() {
|
||||
client.disconnect();
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue