Skip to content

Backend Architecture

The YeboJobs backend is a Node.js/Express API built with TypeScript, using Prisma as the ORM for PostgreSQL.

Tech Stack

  • Runtime: Node.js 20+
  • Framework: Express.js with TypeScript
  • Database: PostgreSQL (Neon Serverless)
  • ORM: Prisma
  • Authentication: JWT (access + refresh tokens)
  • Validation: Joi
  • Payments: Stripe

Project Structure

backend/
├── prisma/
│   └── schema.prisma        # Database schema
├── src/
│   ├── app.ts               # Express app configuration
│   ├── config/
│   │   └── prisma.ts        # Prisma client singleton
│   ├── routes/
│   │   ├── index.ts         # Route aggregator
│   │   ├── auth.routes.ts   # Authentication routes
│   │   ├── job.routes.ts    # Job CRUD routes
│   │   ├── user.routes.ts   # User profile routes
│   │   └── ...              # Other route files
│   ├── controllers/
│   │   ├── auth.controller.ts
│   │   ├── job.controller.ts
│   │   └── ...
│   ├── services/
│   │   ├── auth.service.ts
│   │   ├── job.service.ts
│   │   └── ...
│   ├── middleware/
│   │   ├── auth.middleware.ts
│   │   └── validation.middleware.ts
│   └── utils/
│       ├── jwt.ts
│       ├── ApiResponse.ts
│       └── currencies.ts
└── package.json

Application Setup

The main app.ts configures Express with:

typescript
import express from 'express';
import cors from 'cors';
import routes from '@routes/index';

const app = express();

app.use(cors({
  origin: [
    'http://localhost:5173',
    'https://yebojobs.pages.dev',
    'https://yebojobs.com'
  ],
  credentials: true,
}));

app.use(express.json({ limit: '10mb' }));
app.use('/api', routes);

export default app;

API Route Structure

All routes are prefixed with /api:

Route GroupBase PathDescription
Auth/api/authAuthentication & registration
Jobs/api/jobsJob listings CRUD
Users/api/usersUser profiles
Applications/api/applicationsJob applications
Employers/api/employersEmployer profiles
Services/api/servicesService worker profiles
Bookings/api/bookingsService bookings
Messages/api/messagesMessaging system
Credits/api/creditsCredit wallet system
Billing/api/billingStripe subscription plans
Interviews/api/interviewsAI interview integration
Experience/api/experienceExperience Lab tracks

Key Design Patterns

Service Layer Pattern

All business logic is in services. Controllers are thin - they validate input, call services, and format responses:

typescript
// Controller
static async getJob(req: Request, res: Response) {
  const job = await JobService.getJobById(req.params.id);
  if (!job) return ApiResponse.notFound(res, 'Job not found');
  ApiResponse.success(res, job);
}

// Service
static async getJobById(jobId: string) {
  return prisma.job.findUnique({
    where: { id: jobId },
    include: { employer: true }
  });
}

Standardized API Responses

All responses use the ApiResponse utility:

typescript
class ApiResponse {
  static success(res, data, message = 'Success') {
    return res.json({ success: true, data, message });
  }
  static notFound(res, message) {
    return res.status(404).json({ success: false, message });
  }
  // ... etc
}

Token-based Authentication

JWT with access (15m) + refresh (7d) tokens:

typescript
// Generate tokens
const accessToken = JWTUtil.generateAccessToken({ id, type: 'user' });
const refreshToken = JWTUtil.generateRefreshToken({ id, type: 'user' });

// Verify in middleware
const decoded = JWTUtil.verifyAccessToken(token);
req.user = decoded;

Environment Variables

bash
DATABASE_URL=postgresql://...
DIRECT_URL=postgresql://...
JWT_SECRET=your-secret
JWT_REFRESH_SECRET=your-refresh-secret
STRIPE_SECRET_KEY=sk_...
OKIA_API_URL=https://okia-service-...
OKIA_API_KEY=...
FRONTEND_URL=https://yebojobs.pages.dev

Database Connection

Prisma client is a singleton:

typescript
// config/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma = globalForPrisma.prisma || new PrismaClient();

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma;
}

Error Handling

Controllers wrap operations in try-catch:

typescript
try {
  const result = await SomeService.doSomething();
  ApiResponse.success(res, result);
} catch (error: any) {
  ApiResponse.serverError(res, error.message, error);
}

One chat. Everything done.