================================================================================
FILE: tsconfig.json
================================================================================
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"module": "nodenext",
"target": "esnext",
"types": ["node"],
"sourceMap": false,
"declaration": false,
"declarationMap": false,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"strict": true,
"verbatimModuleSyntax": false,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true
},
"include": ["src/**/*"]
}
================================================================================
FILE: package.json
================================================================================
{
"name": "starter-template",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"dev": "concurrently --kill-others \"npm run watch\" \"nodemon dist/index.js\""
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@prisma/client": "^6.19.3",
"bcryptjs": "^3.0.3",
"concurrently": "^9.2.1",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"multer": "^2.1.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.1.0",
"@types/passport": "^1.0.17",
"@types/passport-jwt": "^4.0.1",
"nodemon": "^3.1.14",
"ts-node": "^10.9.2",
"typescript": "^6.0.3"
}
}
================================================================================
FILE: .env.example
================================================================================
PORT =
================================================================================
FILE: .gitignore
================================================================================
node_modules
/dist
.env
/src/generated/prisma
README.md
================================================================================
FILE: prisma/schema.prisma
================================================================================
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
profileImage String? @map("profile_image")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
refreshTokens RefreshToken[]
galleryImages GalleryImage[]
@@map("users")
}
model RefreshToken {
id String @id @default(uuid())
token String
userId String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("refresh_tokens")
}
model GalleryImage {
id String @id @default(uuid())
imageUrl String @map("image_url")
userId String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("gallery_images")
}
================================================================================
FILE: src/app.ts
================================================================================
import express, { Express, Request, Response } from "express";
import errorMiddleware from "./shared/middlewares/error.middleware";
import path from "path";
import mainRouter from "./routes";
const app: Express = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get("/", (req, res) => {
res.send("Welcome to the Express server!");
});
app.get("/health", (_req: Request, res: Response) => {
return res.json({ status: "ok", timestamp: new Date().toISOString() });
});
app.use(mainRouter);
app.use("/public", express.static(path.join(__dirname, "../public")));
app.use(errorMiddleware);
export default app;
================================================================================
FILE: src/index.ts
================================================================================
import { serverConfig } from "./shared/config";
import "./shared/config/jwtPassport.config";
import app from "./app";
const PORT = serverConfig.port;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
process.on("uncaughtException", (err) => {
console.error("UNCAUGHT EXCEPTION:", err);
process.exit(1);
});
process.on("unhandledRejection", (err) => {
console.error("UNHANDLED REJECTION:", err);
process.exit(1);
});
================================================================================
FILE: src/types/express.d.ts
================================================================================
// src/types/express.d.ts
import { User as PrismaUser } from "@prisma/client";
declare global {
namespace Express {
interface User extends PrismaUser {}
interface Request {
validated: {
body: unknown;
query: unknown;
params: unknown;
};
user?: User;
file?: Multer.File;
files?: Multer.File[];
}
}
}
export {};
================================================================================
FILE: src/shared/config/index.ts
================================================================================
import "dotenv/config";
type ServerConfig = {
port: number;
};
export const serverConfig: ServerConfig = {
port: Number(process.env.PORT) || 3000,
};
================================================================================
FILE: src/shared/config/jwtPassport.config.ts
================================================================================
import passport from "passport";
import {
ExtractJwt,
Strategy,
StrategyOptions,
VerifiedCallback,
} from "passport-jwt";
import prisma from "../libs/prismaClient";
interface JwtPayload {
userId: string;
email: string;
iat?: number;
exp?: number;
}
const options: StrategyOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET!,
};
passport.use(
"jwt",
new Strategy(
options,
async (payload: JwtPayload, done: VerifiedCallback): Promise<void> => {
try {
if (!payload?.userId) {
return done(null, false);
}
const user = await prisma.user.findUnique({
where: {
id: payload.userId,
},
});
if (!user) {
return done(null, false);
}
return done(null, user);
} catch (error) {
return done(error as Error, false);
}
},
),
);
export default passport;
================================================================================
FILE: src/shared/errors/appError.ts
================================================================================
export class AppError extends Error {
public statusCode: number;
public isOperational: boolean;
constructor(message: string, statusCode = 500) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
================================================================================
FILE: src/shared/libs/prismaClient.ts
================================================================================
import { PrismaClient } from "@prisma/client";
const globalForPrisma = global as unknown as {
prisma: PrismaClient;
};
const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log:
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
// Handle graceful shutdown
process.on("SIGINT", async () => {
await prisma.$disconnect();
process.exit(0);
});
process.on("SIGTERM", async () => {
await prisma.$disconnect();
process.exit(0);
});
export default prisma;
================================================================================
FILE: src/shared/middlewares/auth.middleware.ts
================================================================================
import { Request, Response, NextFunction } from "express";
import passport from "passport";
import { User } from "@prisma/client";
const authentication = (
req: Request,
res: Response,
next: NextFunction,
): void => {
passport.authenticate(
"jwt",
{ session: false },
(err: Error | null, user: User | false) => {
if (err) {
return next(err);
}
if (!user) {
res.status(401).json({
success: false,
message: "Unauthorized",
});
return;
}
(req as Request).user = user;
next();
},
)(req, res, next);
};
export default authentication;
================================================================================
FILE: src/shared/middlewares/error.middleware.ts
================================================================================
import { Request, Response, NextFunction } from "express";
const errorMiddleware = (
err: any,
_req: Request,
res: Response,
_next: NextFunction,
) => {
let statusCode = err.statusCode || 500;
let message = err.message || "Something went wrong";
// Prisma errors handling
if (err.code === "P2002") {
statusCode = 400;
message = "Duplicate field value";
}
// Development vs Production
if (process.env.NODE_ENV === "development") {
return res.status(statusCode).json({
success: false,
message,
stack: err.stack,
error: err,
});
}
// Production response
return res.status(statusCode).json({
success: false,
message,
});
};
export default errorMiddleware;
================================================================================
FILE: src/shared/middlewares/upload.middleware.ts
================================================================================
import fs from "fs";
import path from "path";
import multer from "multer";
import { Request } from "express";
const storage = (destination: string) =>
multer.diskStorage({
destination: (req: Request, file: Express.Multer.File, cb) => {
fs.mkdirSync(destination, { recursive: true });
cb(null, destination);
},
filename: (req: Request, file: Express.Multer.File, cb) => {
const ext = path.extname(file.originalname);
const fileName = `${file.fieldname}-${Date.now()}${ext}`;
cb(null, fileName);
},
});
export const uploadSingle = (fieldName: string, destination: string) => {
return multer({
storage: storage(destination),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
},
}).single(fieldName);
};
export const uploadMultiple = (
fieldName: string,
destination: string,
maxCount = 5,
) => {
return multer({
storage: storage(destination),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
},
}).array(fieldName, maxCount);
};
================================================================================
FILE: src/shared/utils/catchAsync.ts
================================================================================
import { Request, Response, NextFunction } from "express";
export const catchAsync =
(fn: any) => (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
================================================================================
FILE: src/modules/auth/auth.service.ts
================================================================================
import { AppError } from "../../shared/errors/appError";
import prisma from "../../shared/libs/prismaClient";
import bcrypt from "bcryptjs";
import jwt, { SignOptions } from "jsonwebtoken";
import { StringValue } from "ms";
class authService {
private readonly jwtSecret: string;
private readonly jwtRefreshSecret: string;
private readonly jwtExpiresIn: string;
private readonly jwtRefreshExpiresIn: string;
private readonly bcryptRounds: number;
constructor() {
this.jwtSecret = process.env.JWT_SECRET!;
this.jwtRefreshSecret = process.env.JWT_REFRESH_SECRET!;
this.jwtExpiresIn = process.env.JWT_EXPIRES_IN! || "15m";
this.jwtRefreshExpiresIn = process.env.JWT_REFRESH_EXPIRES_IN! || "7d";
this.bcryptRounds = Number(process.env.BCRYPT_ROUNDS! || 10);
}
/**
* @description: Registration
* @param data
* @returns
*/
async register(data: any) {
const existingUser = await prisma.user.findUnique({
where: { email: data.email },
});
if (existingUser) {
throw new AppError("User already exists", 409);
}
const hashedPassword = await bcrypt.hash(data.password, this.bcryptRounds);
const user = await prisma.user.create({
data: {
email: data.email,
password: hashedPassword,
},
});
return this.generateTokens(user.id, user.email);
}
/**
* @description: Generate Access and Refresh Tokens
* @param userId
* @param email
* @returns
*/
async generateTokens(userId: string, email: string) {
const payload = { userId, email };
const accessTokenOptions: SignOptions = {
expiresIn: this.jwtExpiresIn as StringValue,
};
const accessToken = jwt.sign(payload, this.jwtSecret, accessTokenOptions);
const refreshTokenOptions: SignOptions = {
expiresIn: this.jwtRefreshExpiresIn as StringValue,
};
const refreshToken = jwt.sign(
payload,
this.jwtRefreshSecret,
refreshTokenOptions,
);
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7); // Set expiration to 7 days
await prisma.refreshToken.create({
data: {
token: refreshToken,
userId,
// expiresAt,
},
});
return { accessToken, refreshToken };
}
}
export default new authService();
================================================================================
FILE: src/modules/auth/auth.controller.ts
================================================================================
import authService from "./auth.service";
import { Request, Response } from "express";
class authController {
/**
* @description: Registration
* @param req
* @param res
* @returns
*/
async register(req: Request, res: Response) {
const data = await authService.register(req.body);
return res.send(data);
}
}
export default new authController();
================================================================================
FILE: src/modules/auth/auth.routes.ts
================================================================================
import authController from "./auth.controller";
import { Router } from "express";
import { catchAsync } from "../../shared/utils/catchAsync";
const routes = Router();
routes.post("/register", catchAsync(authController.register));
export default routes;
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)