feat(backend): bootstrap express app with error handling
This commit is contained in:
39
packages/backend/package.json
Normal file
39
packages/backend/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@flashcard/backend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"start": "node dist/index.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "tsx src/db/migrate.ts",
|
||||
"db:seed": "tsx src/db/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flashcard/shared": "*",
|
||||
"better-sqlite3": "^11.0.0",
|
||||
"drizzle-orm": "^0.33.0",
|
||||
"express": "^4.19.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.0",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/multer": "^1.4.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"drizzle-kit": "^0.24.0",
|
||||
"supertest": "^7.0.0",
|
||||
"tsx": "^4.16.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vitest": "^2.0.0"
|
||||
}
|
||||
}
|
||||
33
packages/backend/src/app.ts
Normal file
33
packages/backend/src/app.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import express, { type Express, type NextFunction, type Request, type Response } from 'express';
|
||||
import { ZodError } from 'zod';
|
||||
import { ApiError } from './lib/errors.js';
|
||||
|
||||
export function createApp(): Express {
|
||||
const app = express();
|
||||
app.use(express.json({ limit: '5mb' }));
|
||||
|
||||
app.get('/api/health', (_req, res) => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Routes mounted in later tasks.
|
||||
|
||||
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
||||
if (err instanceof ZodError) {
|
||||
res.status(400).json({
|
||||
error: { code: 'VALIDATION_ERROR', message: 'Invalid input', details: err.flatten() },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (err instanceof ApiError) {
|
||||
res.status(err.status).json({
|
||||
error: { code: err.code, message: err.message, details: err.details },
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.error(err);
|
||||
res.status(500).json({ error: { code: 'INTERNAL', message: 'Internal server error' } });
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
7
packages/backend/src/index.ts
Normal file
7
packages/backend/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createApp } from './app.js';
|
||||
|
||||
const PORT = Number(process.env.PORT ?? 3000);
|
||||
const app = createApp();
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Backend listening on http://localhost:${PORT}`);
|
||||
});
|
||||
20
packages/backend/src/lib/errors.ts
Normal file
20
packages/backend/src/lib/errors.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public code: string,
|
||||
message: string,
|
||||
public details?: unknown
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
static notFound(what = 'Resource') {
|
||||
return new ApiError(404, 'NOT_FOUND', `${what} not found`);
|
||||
}
|
||||
static validation(message: string, details?: unknown) {
|
||||
return new ApiError(400, 'VALIDATION_ERROR', message, details);
|
||||
}
|
||||
static conflict(message: string) {
|
||||
return new ApiError(409, 'CONFLICT', message);
|
||||
}
|
||||
}
|
||||
10
packages/backend/tsconfig.json
Normal file
10
packages/backend/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
9
packages/backend/vitest.config.ts
Normal file
9
packages/backend/vitest.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
pool: 'forks',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user