Solution to Messy Backend

Past 3 weeks, I was actively developing our sales field app for internal usage. Although I have broad idea how it will work, but I am not a programmer. So, eventually, while it took me 2 weeks to work with Cursor on front end, the backend is even harder.

First, I was using JSON-server then I figure out, there is so many limitation and decided to proceed with local MariaDB. Then, shit hit the fan.

Long story short, our sifu come to me.

Here you go..

What is This Step? Defining the Backend Development Process

This step is Backend Architecture Planning before we start coding.

Since we are building with AI, we must define every stage to avoid future issues and ensure the AI can generate the right components.

πŸ”Ή The Backend Development Process (Step-by-Step)

Below is the structured development process to ensure smooth execution.

πŸ“Œ Step 1: Define the Database Schema (What Data We Store)

Why?

β€’ This defines the structure of data (tables, fields, relationships).

β€’ The AI will use this schema to generate database queries, API responses, and validation rules.

βœ… Deliverable:

β€’ A TypeScript-based schema using Kysely

β€’ Includes tables, columns, data types, foreign keys, and constraints

Example:

interface UsersTable {
  id: string
  email: string
  password: string
  first_name: string
  last_name: string
  role: 'ADMIN' | 'SALESPERSON'
  created_at: Generated<Date>
  updated_at: Generated<Date>
}

πŸ“Œ Step 2: Define the Validation Layer (What Data is Allowed)

Why?

β€’ Ensures only valid data is accepted into the system.

β€’ Prevents bad requests, security risks, and incorrect data entry.

βœ… Deliverable:

β€’ Zod validation schemas to define the rules for API input

β€’ Enforces length, format, required fields, enums, etc.

Example:

import { z } from 'zod'

export const CreateUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  first_name: z.string().min(2),
  last_name: z.string().min(2),
  role: z.enum(['ADMIN', 'SALESPERSON'])
})

πŸ“Œ Step 3: Define Data Transfer Objects (How Data is Sent & Received)

Why?

β€’ Ensures consistent API responses

β€’ Prevents sensitive data leaks (e.g., don’t send password in responses)

β€’ Converts database data into readable formats for the frontend

βœ… Deliverable:

β€’ DTOs for API responses

Example:

export interface UserDTO {
  id: string
  email: string
  fullName: string
  role: string
}

export function toUserDTO(user: User): UserDTO {
  return {
    id: user.id,
    email: user.email,
    fullName: `${user.first_name} ${user.last_name}`,
    role: user.role
  }
}

πŸ“Œ Step 4: Define Routes & Endpoints (How the API Works)

Why?

β€’ Ensures we know which API endpoints exist before implementation

β€’ Defines what each API expects as input and returns as output

βœ… Deliverable:

β€’ A route definition document

Example:

EndpointMethodDescription
/usersGETGet all users
/users/:idGETGet a single user
/usersPOSTCreate a new user
/users/:idPUTUpdate user details
/users/:idDELETEDelete a user

πŸ“Œ Step 5: Define Controllers (Handling API Requests)

Why?

β€’ Ensures a clear separation between API handling and business logic

βœ… Deliverable:

β€’ Controllers that receive API requests, validate input, and call the correct service

Example:

import express from 'express'
import { UserService } from '../services/user-service'
import { CreateUserSchema } from '../validation/user-validation'

const router = express.Router()

router.post('/', async (req, res) => {
  const parsed = CreateUserSchema.safeParse(req.body)
  if (!parsed.success) {
    return res.status(400).json({ error: parsed.error })
  }

  const newUser = await UserService.createUser(parsed.data)
  res.status(201).json(newUser)
})

export default router

πŸ“Œ Step 6: Define Services (Handling Business Logic & Database)

Why?

β€’ Services handle the real logic of the app

β€’ They connect controllers to the database

βœ… Deliverable:

β€’ Services that process data and communicate with the database

Example:

import { db } from '../config/database'
import { NewUser, User } from '../types/database-types'

export class UserService {
  static async createUser(data: NewUser): Promise<User> {
    return await db.insertInto('users').values(data).returningAll().executeTakeFirstOrThrow()
  }

  static async getAllUsers(): Promise<User[]> {
    return await db.selectFrom('users').selectAll().execute()
  }
}

πŸ“Œ Step 7: Define Error Handling (How Errors are Managed)

Why?

β€’ Prevents unexpected crashes

β€’ Ensures consistent error messages

βœ… Deliverable:

β€’ Custom error classes and a global error handler

Example:

class NotFoundError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'NotFoundError'
  }
}

app.use((err, req, res, next) => {
  if (err instanceof NotFoundError) {
    return res.status(404).json({ error: err.message })
  }
  res.status(500).json({ error: 'Internal Server Error' })
})

πŸ“Œ Step 8: Define Security & Authentication

Why?

β€’ Ensures only authorized users can access certain endpoints

β€’ Prevents unauthorized data access

βœ… Deliverable:

β€’ JWT authentication system

β€’ Role-based access control (RBAC)

Example:

import jwt from 'jsonwebtoken'

export function authenticateToken(req, res, next) {
  const token = req.header('Authorization')?.split(' ')[1]
  if (!token) return res.status(401).json({ error: 'Unauthorized' })

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET)
    req.user = user
    next()
  } catch {
    res.status(403).json({ error: 'Invalid token' })
  }
}

πŸ“Œ Step 9: Define API Documentation (Swagger/OpenAPI)

Why?

β€’ Ensures everyone understands the API structure

β€’ Helps frontend developers integrate faster

βœ… Deliverable:

β€’ Swagger/OpenAPI documentation

Example:

import swaggerJsdoc from 'swagger-jsdoc'

const options = {
  definition: {
    openapi: '3.0.0',
    info: { title: 'Sales Field App API', version: '1.0.0' }
  },
  apis: ['./routes/*.js']
}

const swaggerSpec = swaggerJsdoc(options)
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))

πŸš€ Summary: Full Backend Process

StepWhat It DefinesWhy It’s Needed
1. Define Database SchemaData structure (tables, relationships)Prevents database issues
2. Define Validation RulesWhat data is allowedPrevents bad input
3. Define DTOsHow data is sent/receivedEnsures consistent API responses
4. Define API RoutesAPI structure (endpoints, methods)Keeps API design consistent
5. Define ControllersHandles HTTP requestsKeeps logic organized
6. Define ServicesBusiness logic & database interactionsAvoids duplicate code
7. Define Error HandlingGlobal error managementPrevents crashes
8. Define SecurityAuthentication & authorizationProtects user data
9. Define API DocumentationSwagger/OpenAPI setupHelps developers understand the API

From here, then I realize, this is what we need.

Then I asked Cursor to update the progress, here’s the details

# Project Progress

## Completed
- Backend Setup
  - βœ… Express server configuration
  - βœ… Database connection with Kysely
  - βœ… Environment variables setup
  - βœ… Request validation middleware with Zod
  - βœ… Error handling standardization
  - βœ… Route parameter validation
  - βœ… Response serialization
  - βœ… Rate limiting and security headers
  - βœ… Request logging middleware
  - βœ… Authentication middleware
  - βœ… Role-based authorization
  - βœ… Hierarchical access control
  - Implemented Routes:
    - βœ… Auth routes (login, register, verify)
    - βœ… User routes (CRUD with role management)
    - βœ… Market routes (CRUD with currency/timezone)
    - βœ… Country routes (CRUD with market association)
    - βœ… Territory routes (CRUD with location)
    - βœ… Visit routes (CRUD with check-in/out)
    - βœ… Check-in routes (CRUD with location)
    - βœ… Customer routes (CRUD with search)
    - βœ… Survey routes (CRUD with versioning)

- Database Setup
  - βœ… Schema design
  - βœ… Migration system
  - βœ… Base migrations
  - βœ… Admin user seeding
  - βœ… Table relationships
  - βœ… Indexes and constraints

- Frontend Components
  - βœ… Authentication flows
  - βœ… Form components with shadcn
  - βœ… Data tables with sorting and filtering
  - βœ… User management interface
  - βœ… Role hierarchy management
  - βœ… Password management
  - βœ… Status management
  - βœ… Error handling and validation
  - βœ… Toast notifications
  - βœ… Loading states
  - βœ… Responsive layouts

## In Progress
- Frontend Features
  - 🟑 Dashboard layouts
  - 🟑 Survey builder
  - 🟑 Visit scheduler
  - 🟑 Check-in interface
  - 🟑 Customer management
  - 🟑 Analytics dashboard
  - 🟑 Territory mapping
  - 🟑 Mobile responsiveness
  - 🟑 Offline support

- API Enhancements
  - 🟑 File upload middleware for photos
  - 🟑 Batch operations support
  - 🟑 API versioning strategy
  - 🟑 Rate limiting per endpoint
  - 🟑 Cache control headers
  - 🟑 Response compression
  - 🟑 WebSocket integration for real-time updates

## Pending
- Data Seeding
  - Test data generation
  - Sample surveys
  - Demo accounts
  - Example visits/check-ins

- Deployment
  - Docker configuration
  - CI/CD pipeline
  - Production environment
  - Backup strategy
  - Monitoring setup
  - Performance optimization

## Validation Rules
All DTOs use Zod for runtime validation with the following key rules:

### Common Rules
- IDs: 12-character strings (nanoid format)
- Names: Minimum 2 characters
- Dates: ISO format with timezone
- Phone numbers: E.164 format
- Metadata: Optional JSON objects

### Security Rules
- Passwords:
  - Minimum 8 characters
  - Maximum 20 characters
  - Must contain uppercase
  - Must contain lowercase
  - Must contain number
  - Must contain special character
- Email: Valid email format
- JWT: 24-hour expiry

### Business Rules
- Markets:
  - Currency: ISO 4217 (3 characters)
  - Timezone: Valid IANA timezone
  - Code: 2-10 characters

- Customers:
  - Location: Valid lat/long coordinates
  - At least one contact
  - Valid business identification
  - Contact phone in E.164 format
  - Primary contact required
  - Unique contact email
  - Business type validation
  - Soft deletion with visit checks

- Visits:
  - Valid scheduled datetime
  - At least one objective
  - Location verification on check-in
  - Duration tracking
  - Status transitions validation
  - Survey completion tracking

- Check-ins:
  - High accuracy location verification
  - Photo upload support
  - Duration calculation
  - Shop details validation
  - Survey completion status

## Type Safety Findings
### Kysely Query Patterns
1. String References:
   ```typescript
   // Use expression builder for column references
   .where((eb) => eb('column', '=', value))
   ```

2. Update Expressions:
   ```typescript
   // Use type assertion for complex updates
   .set({
     column: value
   } as any)

   // Or use raw SQL for better type safety
   sql`UPDATE table SET column = ${value}`
   ```

3. Join References:
   ```typescript
   // Use raw SQL for complex joins
   sql`
     SELECT t.*, COUNT(c.id) as count
     FROM table1 t
     LEFT JOIN table2 c ON t.id = c.table1_id
   `
   ```

4. Count Expressions:
   ```typescript
   // Use function builder for simple counts
   .select(({ fn }) => [
     fn.count('id').as('count')
   ])

   // Use raw SQL for complex counts
   sql`COUNT(DISTINCT CASE WHEN status = 'active' THEN id END)`
   ```

## Next Steps
1. βœ… Set up OpenAPI/Swagger documentation
2. βœ… Implement comprehensive request validation
3. βœ… Standardize error handling across routes
4. βœ… Add API security measures
5. 🟑 Complete frontend features
6. 🟑 Implement offline support
7. 🟑 Set up deployment pipeline

## Recent Updates
- βœ… Completed user management system with:
  - Role hierarchy management
  - Manager assignment validation
  - Status management
  - Password handling
  - Form validation
  - Data table with filters
  - Error handling
  - Toast notifications
- βœ… Implemented type-safe database operations
- βœ… Added proper error handling and validation
- βœ… Optimized complex queries with raw SQL
- βœ… Implemented API route handlers with:
  - Type-safe request validation using Zod
  - Standardized error handling with custom AppError
  - Request logging with correlation IDs
  - Role-based access control
  - Hierarchical permissions (Market β†’ Country β†’ Territory)
  - Rate limiting and security headers
  - OpenAPI/Swagger documentation
- βœ… Completed survey management features:
  - CRUD operations with JSON field handling
  - Survey state management (draft, published, archived)
  - Market-specific default surveys
  - Survey duplication with version control
  - Type-safe question validation
  - Response tracking and dependency checks 

Long story short, once we developed the front-end, then getting hands dirty on backend is separate matters.

Luckily the 9-steps solution does wonder at this stage. I guess, being very details, grumpy at the same time really help our AI brother to code well.

Images

Leave a Reply

Your email address will not be published. Required fields are marked *