Skip to content

Commit f6fc9c9

Browse files
committed
Add Prisma and PostgreSQL API template
1 parent c19c65c commit f6fc9c9

25 files changed

Lines changed: 445 additions & 48 deletions

File tree

dist/templates/api/README.md

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,40 @@
11
# {{PROJECT_DISPLAY_NAME}}
22

3-
Production-oriented Fastify API starter generated with LaunchStack CLI.
3+
Production-oriented Fastify and PostgreSQL API starter generated with LaunchStack CLI.
44

55
## Included
66

77
- Fastify server bootstrap
88
- TypeScript
9+
- Prisma ORM
10+
- PostgreSQL
11+
- Docker Compose database service
912
- Environment configuration
1013
- Structured logging
1114
- CORS support
12-
- Sensible HTTP utilities
13-
- Central error handler
14-
- Structured not-found responses
15-
- Health endpoint
15+
- Central error handling
16+
- Health endpoints
1617
- Graceful shutdown
1718
- Vitest integration tests
1819

1920
## Requirements
2021

21-
Node.js 20 or newer.
22+
- Node.js 20 or newer
23+
- PostgreSQL or Docker
2224

2325
## Setup
2426

2527
Install dependencies:
2628

2729
npm install
2830

29-
Create the environment file:
31+
Start PostgreSQL:
3032

31-
cp .env.example .env
33+
npm run db:up
34+
35+
Create the initial migration:
36+
37+
npm run prisma:migrate -- --name init
3238

3339
Start development mode:
3440

@@ -38,9 +44,32 @@ The API starts on:
3844

3945
http://localhost:3000
4046

41-
Health endpoint:
47+
Health endpoints:
4248

4349
GET http://localhost:3000/health
50+
GET http://localhost:3000/health/database
51+
52+
## Database Commands
53+
54+
Generate Prisma Client:
55+
56+
npm run prisma:generate
57+
58+
Create a development migration:
59+
60+
npm run prisma:migrate
61+
62+
Apply production migrations:
63+
64+
npm run prisma:deploy
65+
66+
Open Prisma Studio:
67+
68+
npm run prisma:studio
69+
70+
Stop PostgreSQL:
71+
72+
npm run db:down
4473

4574
## Quality Commands
4675

@@ -52,7 +81,7 @@ Run type checking:
5281

5382
npm run typecheck
5483

55-
Build the project:
84+
Build:
5685

5786
npm run build
5887

@@ -62,24 +91,20 @@ Start the production build:
6291

6392
## Structure
6493

94+
prisma/
95+
schema.prisma
6596
src/
6697
config/
67-
env.ts
98+
lib/
99+
prisma.ts
68100
plugins/
69-
config.ts
70-
cors.ts
71-
error-handler.ts
72-
index.ts
73-
sensible.ts
101+
database.ts
74102
routes/
75-
health.ts
76-
index.ts
77103
types/
78-
fastify.d.ts
79104
app.ts
80105
server.ts
81106
tests/
82-
health.test.ts
107+
docker-compose.yml
83108

84109
## Generated by
85110

dist/templates/api/_env

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
NODE_ENV=development
2+
HOST=0.0.0.0
3+
PORT=3000
4+
LOG_LEVEL=info
5+
CORS_ORIGIN=*
6+
DATABASE_URL=postgresql://launchstack:launchstack@localhost:5432/{{PROJECT_NAME}}?schema=public

dist/templates/api/_env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ HOST=0.0.0.0
33
PORT=3000
44
LOG_LEVEL=info
55
CORS_ORIGIN=*
6+
DATABASE_URL=postgresql://launchstack:launchstack@localhost:5432/{{PROJECT_NAME}}?schema=public
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
services:
2+
postgres:
3+
image: postgres:16-alpine
4+
container_name: {{PROJECT_NAME}}-postgres
5+
restart: unless-stopped
6+
environment:
7+
POSTGRES_USER: launchstack
8+
POSTGRES_PASSWORD: launchstack
9+
POSTGRES_DB: {{PROJECT_NAME}}
10+
ports:
11+
- "5432:5432"
12+
volumes:
13+
- postgres_data:/var/lib/postgresql/data
14+
healthcheck:
15+
test:
16+
- CMD-SHELL
17+
- pg_isready -U launchstack -d {{PROJECT_NAME}}
18+
interval: 5s
19+
timeout: 5s
20+
retries: 10
21+
22+
volumes:
23+
postgres_data:

dist/templates/api/package.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,30 @@
55
"description": "{{PROJECT_DISPLAY_NAME}} backend API generated with LaunchStack CLI.",
66
"scripts": {
77
"dev": "tsx watch src/server.ts",
8-
"build": "tsc -p tsconfig.json",
8+
"build": "prisma generate && tsc -p tsconfig.json",
99
"start": "node dist/server.js",
10-
"typecheck": "tsc --noEmit",
11-
"test": "vitest run"
10+
"typecheck": "prisma generate && tsc --noEmit",
11+
"test": "vitest run",
12+
"postinstall": "prisma generate",
13+
"prisma:generate": "prisma generate",
14+
"prisma:migrate": "prisma migrate dev",
15+
"prisma:deploy": "prisma migrate deploy",
16+
"prisma:studio": "prisma studio",
17+
"db:up": "docker compose up -d postgres",
18+
"db:down": "docker compose down",
19+
"db:logs": "docker compose logs -f postgres"
1220
},
1321
"dependencies": {
1422
"@fastify/cors": "^11.1.0",
1523
"@fastify/sensible": "^6.0.3",
24+
"@prisma/client": "^6.0.0",
25+
"dotenv": "^16.4.0",
1626
"fastify": "^5.6.1",
1727
"fastify-plugin": "^5.0.1"
1828
},
1929
"devDependencies": {
2030
"@types/node": "^25.0.0",
31+
"prisma": "^6.0.0",
2132
"tsx": "^4.20.0",
2233
"typescript": "^6.0.0",
2334
"vitest": "^4.0.0"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
generator client {
2+
provider = "prisma-client-js"
3+
}
4+
5+
datasource db {
6+
provider = "postgresql"
7+
url = env("DATABASE_URL")
8+
}
9+
10+
model User {
11+
id String @id @default(cuid())
12+
email String @unique
13+
name String?
14+
createdAt DateTime @default(now())
15+
updatedAt DateTime @updatedAt
16+
17+
@@map("users")
18+
}

dist/templates/api/src/config/env.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import "dotenv/config";
2+
13
export type AppEnvironment = {
24
nodeEnv: "development" | "test" | "production";
35
host: string;
46
port: number;
57
logLevel: string;
68
corsOrigin: string;
9+
databaseUrl: string;
710
};
811

912
function parsePort(value: string | undefined): number {
@@ -34,12 +37,27 @@ function parseNodeEnvironment(
3437
return environment;
3538
}
3639

40+
function requireEnvironmentVariable(
41+
name: string,
42+
value: string | undefined
43+
): string {
44+
if (!value || !value.trim()) {
45+
throw new Error(`${name} is required.`);
46+
}
47+
48+
return value;
49+
}
50+
3751
export function loadEnvironment(): AppEnvironment {
3852
return {
3953
nodeEnv: parseNodeEnvironment(process.env.NODE_ENV),
4054
host: process.env.HOST ?? "0.0.0.0",
4155
port: parsePort(process.env.PORT),
4256
logLevel: process.env.LOG_LEVEL ?? "info",
43-
corsOrigin: process.env.CORS_ORIGIN ?? "*"
57+
corsOrigin: process.env.CORS_ORIGIN ?? "*",
58+
databaseUrl: requireEnvironmentVariable(
59+
"DATABASE_URL",
60+
process.env.DATABASE_URL
61+
)
4462
};
4563
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { PrismaClient } from "@prisma/client";
2+
3+
declare global {
4+
var launchStackPrisma: PrismaClient | undefined;
5+
}
6+
7+
export const prisma =
8+
globalThis.launchStackPrisma ??
9+
new PrismaClient({
10+
log:
11+
process.env.NODE_ENV === "development"
12+
? ["warn", "error"]
13+
: ["error"]
14+
});
15+
16+
if (process.env.NODE_ENV !== "production") {
17+
globalThis.launchStackPrisma = prisma;
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import fp from "fastify-plugin";
2+
import { prisma } from "../lib/prisma";
3+
4+
export const databasePlugin = fp(
5+
async (app) => {
6+
app.decorate("prisma", prisma);
7+
8+
app.addHook("onClose", async () => {
9+
await prisma.$disconnect();
10+
});
11+
},
12+
{
13+
name: "database",
14+
dependencies: ["config"]
15+
}
16+
);

dist/templates/api/src/plugins/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { FastifyInstance } from "fastify";
22
import { configPlugin } from "./config";
33
import { corsPlugin } from "./cors";
4+
import { databasePlugin } from "./database";
45
import { errorHandlerPlugin } from "./error-handler";
56
import { sensiblePlugin } from "./sensible";
67

78
export async function registerPlugins(
89
app: FastifyInstance
910
): Promise<void> {
1011
await app.register(configPlugin);
12+
await app.register(databasePlugin);
1113
await app.register(sensiblePlugin);
1214
await app.register(corsPlugin);
1315
await app.register(errorHandlerPlugin);

0 commit comments

Comments
 (0)