feat/fix/refactor: bump all packages + add redis and drizzle to request object + use xo instead of eslint + refactor redis to be more viable in prod
This commit is contained in:
parent
ef231be351
commit
a18a442485
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"es2021": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": "xo",
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"extends": ["xo-typescript"],
|
|
||||||
"files": ["*.ts", "*.tsx"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
// double quotes
|
|
||||||
"@typescript-eslint/quotes": "double"
|
|
||||||
}
|
|
||||||
}
|
|
62
package.json
62
package.json
@ -7,7 +7,7 @@
|
|||||||
"author": "Cody Miller <50378828+Looskie@users.noreply.github.com>",
|
"author": "Cody Miller <50378828+Looskie@users.noreply.github.com>",
|
||||||
"license": "none",
|
"license": "none",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "xo",
|
||||||
"dev": "tsx watch --clear-screen=false src/index.ts",
|
"dev": "tsx watch --clear-screen=false src/index.ts",
|
||||||
"prettier": "prettier --write .",
|
"prettier": "prettier --write .",
|
||||||
"migrate": "drizzle-kit generate:pg",
|
"migrate": "drizzle-kit generate:pg",
|
||||||
@ -15,28 +15,50 @@
|
|||||||
"start": "node dist/index.js"
|
"start": "node dist/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cli-color": "^2.0.2",
|
"@types/cli-color": "^2.0.6",
|
||||||
"@types/node": "^20.2.5",
|
"@types/node": "^20.10.4",
|
||||||
"@types/pg": "^8.10.2",
|
"@types/pg": "^8.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": ">=5.57.0",
|
|
||||||
"@typescript-eslint/parser": ">=5.57.0",
|
|
||||||
"cli-color": "^2.0.3",
|
"cli-color": "^2.0.3",
|
||||||
"dotenv": "^16.1.4",
|
"dotenv": "^16.3.1",
|
||||||
"drizzle-kit": "^0.19.12",
|
"drizzle-kit": "^0.20.6",
|
||||||
"eslint": ">=8.0.0",
|
"prettier": "^3.1.1",
|
||||||
"eslint-config-xo": "^0.43.1",
|
"tsc-alias": "^1.8.8",
|
||||||
"eslint-config-xo-typescript": "^0.57.0",
|
|
||||||
"prettier": "^2.8.8",
|
|
||||||
"tsc-alias": "^1.8.6",
|
|
||||||
"tsx": "^3.12.7",
|
"tsx": "^3.12.7",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.3.3",
|
||||||
|
"xo": "^0.56.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.3.0",
|
"@fastify/cors": "^8.4.2",
|
||||||
"drizzle-orm": "^0.28.1",
|
"drizzle-orm": "^0.29.1",
|
||||||
"fastify": "^4.21.0",
|
"fastify": "^4.25.0",
|
||||||
"pg": "^8.11.2",
|
"fastify-plugin": "^4.5.1",
|
||||||
"redis": "^4.6.7",
|
"pg": "^8.11.3",
|
||||||
"zod": "^3.21.4"
|
"redis": "^4.6.11",
|
||||||
|
"tsc": "^2.0.4",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"xo": {
|
||||||
|
"rules": {
|
||||||
|
"import/extensions": "off",
|
||||||
|
"@typescript-eslint/quotes": [
|
||||||
|
"error",
|
||||||
|
"double"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/indent": [
|
||||||
|
"error",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"@typescript-eslint/object-curly-spacing": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"n/prefer-global/process": "off",
|
||||||
|
"@typescript-eslint/no-extraneous-class": "off",
|
||||||
|
"arrow-parens": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-floating-promises": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { drizzle } from "drizzle-orm/node-postgres";
|
|||||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||||
import { Pool } from "pg";
|
import { Pool } from "pg";
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
export let db: ReturnType<typeof drizzle<typeof schema>>;
|
export let db: ReturnType<typeof drizzle<typeof schema>>;
|
||||||
|
|
||||||
export const initDb = async () => {
|
export const initDb = async () => {
|
||||||
@ -16,12 +17,14 @@ export const initDb = async () => {
|
|||||||
|
|
||||||
return client;
|
return client;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
Logger.error("INIT", `Failed to connect to database ${String(err)}}`);
|
Logger.error("INIT", `Failed to connect to database ${String(error)}}`);
|
||||||
process.exit(1);
|
throw new Error(`Failed to connect to database ${String(error)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
db = drizzle(pool);
|
db = drizzle(pool, {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
await migrate(db, {
|
await migrate(db, {
|
||||||
migrationsFolder: "./src/db/migrations",
|
migrationsFolder: "./src/db/migrations",
|
||||||
@ -29,8 +32,8 @@ export const initDb = async () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
Logger.info("INIT", "Migrated database");
|
Logger.info("INIT", "Migrated database");
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
Logger.error("INIT", `Failed to migrate database ${String(err)}`);
|
Logger.error("INIT", `Failed to migrate database ${String(error)}`);
|
||||||
process.exit(1);
|
throw new Error(`Failed to migrate database ${String(error)}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
10
src/fastify.d.ts
vendored
Normal file
10
src/fastify.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { db } from "./db";
|
||||||
|
import type { Redis } from "./utils";
|
||||||
|
|
||||||
|
declare module "fastify" {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
interface FastifyRequest {
|
||||||
|
db: typeof db;
|
||||||
|
redis: typeof Redis;
|
||||||
|
}
|
||||||
|
}
|
11
src/index.ts
11
src/index.ts
@ -2,7 +2,9 @@ import { initDb } from "@api/db";
|
|||||||
import { testRoutes } from "@api/routes";
|
import { testRoutes } from "@api/routes";
|
||||||
import { env, Logger, Redis } from "@api/utils";
|
import { env, Logger, Redis } from "@api/utils";
|
||||||
import fastify from "fastify";
|
import fastify from "fastify";
|
||||||
|
import { middleware } from "./modules/middleware";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const API_VERSION = "v1";
|
const API_VERSION = "v1";
|
||||||
|
|
||||||
export const main = async () => {
|
export const main = async () => {
|
||||||
@ -14,6 +16,7 @@ export const main = async () => {
|
|||||||
await initDb();
|
await initDb();
|
||||||
await Redis.initialize();
|
await Redis.initialize();
|
||||||
|
|
||||||
|
server.register(middleware);
|
||||||
server.register(import("@fastify/cors"), {
|
server.register(import("@fastify/cors"), {
|
||||||
maxAge: 600,
|
maxAge: 600,
|
||||||
origin: true,
|
origin: true,
|
||||||
@ -25,10 +28,10 @@ export const main = async () => {
|
|||||||
prefix: `/${API_VERSION}/test`,
|
prefix: `/${API_VERSION}/test`,
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen({ host: env.HOST, port: env.PORT }, (err, address) => {
|
server.listen({ host: env.HOST, port: env.PORT }, (error, address) => {
|
||||||
if (err) {
|
if (error) {
|
||||||
Logger.error("INIT", err.message);
|
Logger.error("INIT", error.message);
|
||||||
process.exit(1);
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info("INIT", `Server listening at ${address}`);
|
Logger.info("INIT", `Server listening at ${address}`);
|
||||||
|
13
src/modules/middleware.ts
Normal file
13
src/modules/middleware.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import fp from "fastify-plugin";
|
||||||
|
import { Redis } from "../utils";
|
||||||
|
import { db } from "../db";
|
||||||
|
|
||||||
|
const middleware = fp(async (fastify: FastifyInstance, _options: unknown) => {
|
||||||
|
fastify.addHook("onRequest", async (request) => {
|
||||||
|
request.redis = Redis;
|
||||||
|
request.db = db;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export { middleware };
|
@ -1,9 +1,13 @@
|
|||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
|
|
||||||
export const testRoutes = (fastify: FastifyInstance, _: unknown, done: () => void) => {
|
export const testRoutes = (fastify: FastifyInstance, _: unknown, done: () => void) => {
|
||||||
fastify.get("/", async (_req, res) => {
|
fastify.get("/", async (request, response) => {
|
||||||
res.send({
|
const visits = Number(await request.redis.get("visits"));
|
||||||
|
|
||||||
|
request.redis.incr("visits");
|
||||||
|
response.send({
|
||||||
hello: "world",
|
hello: "world",
|
||||||
|
visits,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
|
||||||
const env_schema = z.object({
|
const envSchema = z.object({
|
||||||
DATABASE_URL: z.string().default("postgres://user:password@127.0.0.1:5432/postgres"),
|
DATABASE_URL: z.string().default("postgres://user:password@127.0.0.1:5432/postgres"),
|
||||||
REDIS_URL: z.string().default("redis://127.0.0.1:6379/"),
|
REDIS_URL: z.string().default("redis://127.0.0.1:6379/"),
|
||||||
PORT: z.coerce.number().default(8080),
|
PORT: z.coerce.number().default(8080),
|
||||||
HOST: z.string().default("127.0.0.1"),
|
HOST: z.string().default("127.0.0.1"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const env = env_schema.parse(process.env);
|
export const env = envSchema.parse(process.env);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import * as redis from "redis";
|
import * as redis from "redis";
|
||||||
import { env, Logger } from "@api/utils";
|
import { env, Logger } from "@api/utils";
|
||||||
|
|
||||||
type RedisPrefixes = "session";
|
type RedisPrefixes = "session" | "visits";
|
||||||
|
type RedisKey = `${RedisPrefixes}:${string | number}` | RedisPrefixes;
|
||||||
|
|
||||||
class Redis {
|
class Redis {
|
||||||
public static redis: ReturnType<typeof redis.createClient>;
|
public static redis: ReturnType<typeof redis.createClient>;
|
||||||
@ -11,9 +12,9 @@ class Redis {
|
|||||||
url: env.REDIS_URL,
|
url: env.REDIS_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redis.on("error", (err) => {
|
this.redis.on("error", (error) => {
|
||||||
Logger.error("INIT", "Failed to connect to redis " + String(err));
|
Logger.error("INIT", "Failed to connect to redis " + String(error));
|
||||||
process.exit(1);
|
throw new Error("Failed to connect to redis");
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redis.on("connect", () => {
|
this.redis.on("connect", () => {
|
||||||
@ -23,16 +24,48 @@ class Redis {
|
|||||||
await this.redis.connect();
|
await this.redis.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async set(type: RedisPrefixes, key: string, value: string, options?: redis.SetOptions) {
|
/**
|
||||||
await this.redis.set(`${type}:${key}`, value, options);
|
*
|
||||||
|
* @param key Key of the value to set
|
||||||
|
* @param value Value to set
|
||||||
|
* @param options Options for the value to set
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await Redis.set("session:user_xxx", new Date().toISOString());
|
||||||
|
* await Redis.set("visits", "1");
|
||||||
|
*/
|
||||||
|
public static async set(key: RedisKey, value: string, options?: redis.SetOptions) {
|
||||||
|
await this.redis.set(key, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async get<T>(type: RedisPrefixes, key: string) {
|
public static async incr(key: RedisKey) {
|
||||||
return this.redis.get(`${type}:${key}`) as T | undefined;
|
await this.redis.incr(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async del(type: RedisPrefixes, key: string) {
|
public static async decr(key: RedisKey) {
|
||||||
await this.redis.del(`${type}:${key}`);
|
await this.redis.decr(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param key Key of the value to get
|
||||||
|
* @example
|
||||||
|
* const value = await Redis.get("session:user_xxx");
|
||||||
|
* const visits = await Redis.get("visits");
|
||||||
|
*/
|
||||||
|
public static async get<T = string>(key: RedisKey) {
|
||||||
|
return this.redis.get(key) as T | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param key Key of the value to delete
|
||||||
|
* @example
|
||||||
|
* await Redis.del("session:user_xxx");
|
||||||
|
* await Redis.del("visits");
|
||||||
|
*/
|
||||||
|
public static async del(key: RedisKey) {
|
||||||
|
await this.redis.del(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user