Initial project setup: monorepo, API, Postgres, events CRUD

This commit is contained in:
Media
2026-04-14 19:46:30 -05:00
commit 27c74e0f25
40 changed files with 3447 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
{
"name": "calendar-api",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@fastify/cors": "^11.0.0",
"@fastify/multipart": "^9.0.0",
"@fastify/static": "^8.0.0",
"fastify": "^5.0.0",
"pg": "^8.20.0"
},
"devDependencies": {
"@types/node": "^24.0.0",
"@types/pg": "^8.20.0",
"tsx": "^4.20.0",
"typescript": "^5.8.0"
}
}
+11
View File
@@ -0,0 +1,11 @@
import pg from "pg";
const { Pool } = pg;
export const pool = new Pool({
host: "localhost",
port: 5432,
user: "postgres",
password: "postgres",
database: "church_platform",
});
+147
View File
@@ -0,0 +1,147 @@
import { FastifyInstance } from "fastify";
import { pool } from "../../db";
export async function calendarRoutes(app: FastifyInstance) {
app.get("/events", async () => {
const result = await pool.query(
"SELECT * FROM events ORDER BY starts_at ASC"
);
return result.rows;
});
app.post(
"/events",
{
schema: {
body: {
type: "object",
required: ["title", "starts_at"],
properties: {
title: { type: "string", minLength: 1 },
starts_at: { type: "string", format: "date-time" },
},
},
},
},
async (request, reply) => {
const { title, starts_at } = request.body as {
title: string;
starts_at: string;
};
const result = await pool.query(
"INSERT INTO events (title, starts_at) VALUES ($1, $2) RETURNING *",
[title, starts_at]
);
reply.code(201);
return result.rows[0];
}
);
app.get(
"/events/:id",
{
schema: {
params: {
type: "object",
required: ["id"],
properties: {
id: { type: "string" },
},
},
},
},
async (request, reply) => {
const { id } = request.params as { id: string };
const result = await pool.query("SELECT * FROM events WHERE id = $1", [
id,
]);
if (result.rows.length === 0) {
reply.code(404);
return { error: "Event not found" };
}
return result.rows[0];
}
);
app.delete(
"/events/:id",
{
schema: {
params: {
type: "object",
required: ["id"],
properties: {
id: { type: "string" },
},
},
},
},
async (request, reply) => {
const { id } = request.params as { id: string };
const result = await pool.query(
"DELETE FROM events WHERE id = $1 RETURNING *",
[id]
);
if (result.rows.length === 0) {
reply.code(404);
return { error: "Event not found" };
}
return { success: true };
}
);
app.put(
"/events/:id",
{
schema: {
params: {
type: "object",
required: ["id"],
properties: {
id: { type: "string" },
},
},
body: {
type: "object",
required: ["title", "starts_at"],
properties: {
title: { type: "string", minLength: 1 },
starts_at: { type: "string", format: "date-time" },
},
},
},
},
async (request, reply) => {
const { id } = request.params as { id: string };
const { title, starts_at } = request.body as {
title: string;
starts_at: string;
};
const result = await pool.query(
`UPDATE events
SET title = $1,
starts_at = $2,
updated_at = NOW()
WHERE id = $3
RETURNING *`,
[title, starts_at, id]
);
if (result.rows.length === 0) {
reply.code(404);
return { error: "Event not found" };
}
return result.rows[0];
}
);
}
+21
View File
@@ -0,0 +1,21 @@
import Fastify from "fastify";
import { pool } from "./db/index.js";
import { calendarRoutes } from "./modules/calendar/routes.js";
const app = Fastify();
pool
.query("SELECT NOW()")
.then((res) => console.log("DB connected:", res.rows[0]))
.catch((err) => console.error("DB connection failed:", err));
app.register(calendarRoutes);
app.get("/health", async () => {
return { ok: true };
});
app.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
console.log("API running on port 3000");
});
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"types": ["node"]
},
"include": ["src"]
}