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:
Endpoint | Method | Description |
---|---|---|
/users | GET | Get all users |
/users/:id | GET | Get a single user |
/users | POST | Create a new user |
/users/:id | PUT | Update user details |
/users/:id | DELETE | Delete 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
Step | What It Defines | Why Itβs Needed |
---|---|---|
1. Define Database Schema | Data structure (tables, relationships) | Prevents database issues |
2. Define Validation Rules | What data is allowed | Prevents bad input |
3. Define DTOs | How data is sent/received | Ensures consistent API responses |
4. Define API Routes | API structure (endpoints, methods) | Keeps API design consistent |
5. Define Controllers | Handles HTTP requests | Keeps logic organized |
6. Define Services | Business logic & database interactions | Avoids duplicate code |
7. Define Error Handling | Global error management | Prevents crashes |
8. Define Security | Authentication & authorization | Protects user data |
9. Define API Documentation | Swagger/OpenAPI setup | Helps 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




