feat: add Express API base structure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
7
apps/api/.env.example
Normal file
7
apps/api/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
NODE_ENV=development
|
||||
PORT=4000
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/horux360?schema=public"
|
||||
JWT_SECRET=your-super-secret-jwt-key-min-32-chars-long-for-development
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
CORS_ORIGIN=http://localhost:3000
|
||||
36
apps/api/package.json
Normal file
36
apps/api/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@horux/api",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:seed": "tsx prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@horux/shared": "workspace:*",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.0",
|
||||
"helmet": "^8.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"@types/node": "^22.0.0",
|
||||
"prisma": "^5.22.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
31
apps/api/src/app.ts
Normal file
31
apps/api/src/app.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import { env } from './config/env.js';
|
||||
import { errorMiddleware } from './middlewares/error.middleware.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Security
|
||||
app.use(helmet());
|
||||
app.use(cors({
|
||||
origin: env.CORS_ORIGIN,
|
||||
credentials: true,
|
||||
}));
|
||||
|
||||
// Body parsing
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// API Routes (to be added)
|
||||
// app.use('/api/auth', authRoutes);
|
||||
|
||||
// Error handling
|
||||
app.use(errorMiddleware);
|
||||
|
||||
export { app };
|
||||
20
apps/api/src/config/env.ts
Normal file
20
apps/api/src/config/env.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
PORT: z.string().default('4000'),
|
||||
DATABASE_URL: z.string(),
|
||||
JWT_SECRET: z.string().min(32),
|
||||
JWT_EXPIRES_IN: z.string().default('15m'),
|
||||
JWT_REFRESH_EXPIRES_IN: z.string().default('7d'),
|
||||
CORS_ORIGIN: z.string().default('http://localhost:3000'),
|
||||
});
|
||||
|
||||
const parsed = envSchema.safeParse(process.env);
|
||||
|
||||
if (!parsed.success) {
|
||||
console.error('❌ Invalid environment variables:', parsed.error.flatten().fieldErrors);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export const env = parsed.data;
|
||||
9
apps/api/src/index.ts
Normal file
9
apps/api/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { app } from './app.js';
|
||||
import { env } from './config/env.js';
|
||||
|
||||
const PORT = parseInt(env.PORT, 10);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 API Server running on http://localhost:${PORT}`);
|
||||
console.log(`📊 Environment: ${env.NODE_ENV}`);
|
||||
});
|
||||
33
apps/api/src/middlewares/error.middleware.ts
Normal file
33
apps/api/src/middlewares/error.middleware.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export class AppError extends Error {
|
||||
constructor(
|
||||
public statusCode: number,
|
||||
public message: string,
|
||||
public isOperational = true
|
||||
) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, AppError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export function errorMiddleware(
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (err instanceof AppError) {
|
||||
return res.status(err.statusCode).json({
|
||||
status: 'error',
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
|
||||
console.error('Unhandled error:', err);
|
||||
|
||||
return res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Internal server error',
|
||||
});
|
||||
}
|
||||
18
apps/api/tsconfig.json
Normal file
18
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user