This commit is contained in:
Cody Miller 2023-06-09 20:37:36 -04:00
parent f1e2e31237
commit aaadc4f4d6
21 changed files with 2444 additions and 0 deletions

21
.eslintrc.json Normal file
View File

@ -0,0 +1,21 @@
{
"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"
}
}

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
env
yarn-error.log

13
.prettierrc Normal file
View File

@ -0,0 +1,13 @@
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"jsxSingleQuote": false
}

View File

@ -1,2 +1,3 @@
# fastify-drizzle-quick-start
Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM

16
docker-compose.yml Normal file
View File

@ -0,0 +1,16 @@
version: "3.9"
services:
postgres:
image: postgres
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: postgres
ports:
- "5432:5432"
redis:
image: redis
ports:
- "6379:6379"

4
drizzle.config.json Normal file
View File

@ -0,0 +1,4 @@
{
"out": "./src/db/migrations",
"schema": "./src/db/schemas.ts"
}

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "fastify-drizzle-quick-start",
"version": "1.0.0",
"description": "Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM",
"main": "index.js",
"repository": "https://github.com/Looskie/fastify-drizzle-quick-start.git",
"author": "Cody Miller <50378828+Looskie@users.noreply.github.com>",
"license": "none",
"scripts": {
"lint": "eslint . --ext .ts",
"dev": "tsx watch --clear-screen=false src/index.ts",
"prettier": "prettier --write .",
"migrate": "drizzle-kit generate:pg"
},
"devDependencies": {
"@types/cli-color": "^2.0.2",
"@types/node": "^20.2.5",
"@types/pg": "^8.10.2",
"@typescript-eslint/eslint-plugin": ">=5.57.0",
"@typescript-eslint/parser": ">=5.57.0",
"cli-color": "^2.0.3",
"dotenv": "^16.1.4",
"drizzle-kit": "^0.18.1",
"eslint": ">=8.0.0",
"eslint-config-xo": "^0.43.1",
"eslint-config-xo-typescript": "^0.57.0",
"prettier": "^2.8.8",
"tsx": "^3.12.7",
"typescript": ">=4.4"
},
"dependencies": {
"@fastify/cors": "^8.3.0",
"drizzle-orm": "^0.26.5",
"fastify": "^4.18.0",
"pg": "^8.11.0",
"redis": "^4.6.7",
"zod": "^3.21.4"
}
}

36
src/db/index.ts Normal file
View File

@ -0,0 +1,36 @@
import * as schema from "@api/db/schemas";
import { env, Logger } from "@api/utils";
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { Pool } from "pg";
export let db: ReturnType<typeof drizzle<typeof schema>>;
export const initDb = async () => {
const pool = await new Pool({
connectionString: env.DATABASE_URL,
})
.connect()
.then((client) => {
Logger.info("INIT", "Connected to database");
return client;
})
.catch((err) => {
Logger.error("INIT", `Failed to connect to database ${String(err)}}`);
process.exit(1);
});
db = drizzle(pool);
await migrate(db, {
migrationsFolder: "./src/db/migrations",
})
.then(() => {
Logger.info("INIT", "Migrated database");
})
.catch((err) => {
Logger.error("INIT", `Failed to migrate database ${String(err)}`);
process.exit(1);
});
};

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
"full_name" text,
"phone" varchar(256)
);

View File

@ -0,0 +1,42 @@
{
"version": "5",
"dialect": "pg",
"id": "478200dd-a614-41dd-9cd1-f6368405417e",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"full_name": {
"name": "full_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"phone": {
"name": "phone",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -0,0 +1,13 @@
{
"version": "5",
"dialect": "pg",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1686356934306,
"tag": "0000_plain_killmonger",
"breakpoints": false
}
]
}

7
src/db/schemas.ts Normal file
View File

@ -0,0 +1,7 @@
import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
fullName: text("full_name"),
phone: varchar("phone", { length: 256 }),
});

40
src/index.ts Normal file
View File

@ -0,0 +1,40 @@
import { initDb } from "@api/db";
import { testRoutes } from "@api/routes";
import { env, Logger, Redis } from "@api/utils";
import fastify from "fastify";
const API_VERSION = "v1";
export const main = async () => {
const server = fastify({
bodyLimit: 1_000_000,
trustProxy: true,
});
await initDb();
await Redis.initialize();
server.register(import("@fastify/cors"), {
maxAge: 600,
origin: true,
credentials: true,
});
// Routes
server.register(testRoutes, {
prefix: `/${API_VERSION}/test`,
});
server.listen({ host: env.HOST, port: env.PORT }, (err, address) => {
if (err) {
Logger.error("INIT", err.message);
process.exit(1);
}
Logger.info("INIT", `Server listening at ${address}`);
});
return server;
};
main();

1
src/routes/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./test";

11
src/routes/test.ts Normal file
View File

@ -0,0 +1,11 @@
import type { FastifyInstance } from "fastify";
export const testRoutes = (fastify: FastifyInstance, _: unknown, done: () => void) => {
fastify.get("/", async (_req, res) => {
res.send({
hello: "world",
});
});
done();
};

11
src/utils/env.ts Normal file
View File

@ -0,0 +1,11 @@
import { z } from "zod";
import "dotenv/config";
const env_schema = z.object({
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/"),
PORT: z.number().default(8080),
HOST: z.string().default("127.0.0.1"),
});
export const env = env_schema.parse(process.env);

3
src/utils/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from "./env";
export * from "./logger";
export * from "./redis";

17
src/utils/logger.ts Normal file
View File

@ -0,0 +1,17 @@
import clc from "cli-color";
class Logger {
public static info(prefix: string, message: string) {
console.log(`[${clc.cyan(prefix)}] ${message}`);
}
public static error(prefix: string, message: string) {
console.log(`[${clc.red(prefix)}] ${message}`);
}
public static success(prefix: string, message: string) {
console.log(`[${clc.green(prefix)}] ${message}`);
}
}
export { Logger };

39
src/utils/redis.ts Normal file
View File

@ -0,0 +1,39 @@
import * as redis from "redis";
import { env, Logger } from "@api/utils";
type RedisPrefixes = "session";
class Redis {
public static redis: ReturnType<typeof redis.createClient>;
public static async initialize() {
this.redis = redis.createClient({
url: env.REDIS_URL,
});
this.redis.on("error", (err) => {
Logger.error("INIT", "Failed to connect to redis " + String(err));
process.exit(1);
});
this.redis.on("connect", () => {
Logger.info("INIT", "Connected to redis");
});
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);
}
public static async get<T>(type: RedisPrefixes, key: string) {
return this.redis.get(`${type}:${key}`) as T | undefined;
}
public static async del(type: RedisPrefixes, key: string) {
await this.redis.del(`${type}:${key}`);
}
}
export { Redis, type RedisPrefixes };

22
tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ESNext",
"strict": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {
"@api/*": ["src/*"]
}
}
}

2100
yarn.lock Normal file

File diff suppressed because it is too large Load Diff