init
This commit is contained in:
parent
f1e2e31237
commit
aaadc4f4d6
21
.eslintrc.json
Normal file
21
.eslintrc.json
Normal 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
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
env
|
||||||
|
yarn-error.log
|
13
.prettierrc
Normal file
13
.prettierrc
Normal 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
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
# fastify-drizzle-quick-start
|
# fastify-drizzle-quick-start
|
||||||
|
|
||||||
Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM
|
Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM
|
||||||
|
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal 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
4
drizzle.config.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"out": "./src/db/migrations",
|
||||||
|
"schema": "./src/db/schemas.ts"
|
||||||
|
}
|
39
package.json
Normal file
39
package.json
Normal 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
36
src/db/index.ts
Normal 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);
|
||||||
|
});
|
||||||
|
};
|
5
src/db/migrations/0000_plain_killmonger.sql
Normal file
5
src/db/migrations/0000_plain_killmonger.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "users" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"full_name" text,
|
||||||
|
"phone" varchar(256)
|
||||||
|
);
|
42
src/db/migrations/meta/0000_snapshot.json
Normal file
42
src/db/migrations/meta/0000_snapshot.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
13
src/db/migrations/meta/_journal.json
Normal file
13
src/db/migrations/meta/_journal.json
Normal 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
7
src/db/schemas.ts
Normal 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
40
src/index.ts
Normal 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
1
src/routes/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./test";
|
11
src/routes/test.ts
Normal file
11
src/routes/test.ts
Normal 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
11
src/utils/env.ts
Normal 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
3
src/utils/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./env";
|
||||||
|
export * from "./logger";
|
||||||
|
export * from "./redis";
|
17
src/utils/logger.ts
Normal file
17
src/utils/logger.ts
Normal 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
39
src/utils/redis.ts
Normal 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
22
tsconfig.json
Normal 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/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user