SDLC 03: Development Standards and Implementation Guidelines
Revision history: Updated May 2026 — AWS EC2 hosting; Express + Next.js split; PawaPay + Flutterwave payment standards; CommonJS module conventions.
The Pakashop repository is organized as a monorepo containing a backend Express API and a frontend Next.js application. Supporting documents (SDLC, API) are version-controlled alongside the code to ensure documentation stays synchronized with the implementation.
2. Coding Standards
2.1 Module System
The backend uses CommonJS (require / module.exports) throughout. The frontend uses ES Modules (import / export).
- Backend: Uses CommonJS (
require/module.exports) to maintain compatibility with the Node.js runtime and existing microservices. - Frontend: Uses ES Modules (
import/export) for modern build-time optimizations (tree-shaking).
Critical: Never mix
requireandimportwithin the same backend file. New backend files must use CommonJS.
2.2 Import Path Conventions
Backend imports follow these relative depth rules:
| File location | To reach lib/ | Example |
|---|---|---|
src/routes/ | ../lib/ | require('../lib/logger') |
src/controllers/feature/ | ../../lib/ | require('../../lib/prisma') |
src/services/feature/ | ../../lib/ | require('../../lib/logger') |
src/services/payment/webhooks/ | ../../../lib/ | require('../../../lib/prisma') |
Always verify the relative depth before adding a new
require(). Incorrect paths are the most common source of startup crashes.
2.3 Structured Logging
Use the centralised pino logger. Never use console.log in production code.
All logs use the pino structured logger and are shipped to Middleware.io. PII (phone numbers, emails, card data) must be masked at the application layer before any log write occurs.
All logs are shipped to Middleware.io via OpenTelemetry. PII (phone numbers, emails, card data) must be masked before logging — use ComplianceLogger.maskPhone() / ComplianceLogger.maskEmail().
2.4 Observability — Custom Spans
Wrap critical business logic in withSpan to generate traces visible in Middleware.io:
Critical business logic is wrapped in custom spans to provide granular visibility into operation duration and success rates within the APM dashboard.
2.5 Error Handling
- Controller functions must never throw unhandled errors to Express. Always wrap in
try/catch. - Return consistent error shapes:
{ success: false, message: '...' }. - Attach
statusCodeto thrown errors so controllers can forward the correct HTTP status:
Controllers catch exceptions and format them into a consistent error shape (success, message, code) with appropriate HTTP status codes (e.g., 404 for missing resources).
2.6 Prisma
- Run
npx prisma generateafter every schema change. - All database queries use the Prisma client — no raw SQL.
- For transactions, use
prisma.$transaction([...])or the interactive clientprisma.$transaction(async (tx) => { ... }). - Development:
npx prisma migrate dev --name <description>. - Production:
npx prisma migrate deploy. - Never use
prisma db pushin production (it bypasses migration history).
2.7 Payment — Critical Rules
- Card Security: All card entry must occur on hosted gateway pages; Pakashop servers are prohibited from handling raw card data.
- Data Privacy: Phone numbers and emails must be masked before being written to any persistent logs.
3. Environment Variables
Variables are stored in /opt/pakashop/.env.production on EC2 (backend) and Vercel Environment Variables (frontend). Never commit .env files.
The .env.example at the project root documents all required variables with placeholder values.
| Scope | Management method |
|---|---|
| Backend (EC2) | /path/to/.env.production loaded by systemd |
| Frontend (Vercel) | Vercel Project Settings → Environment Variables |
| Local dev | .env.local (gitignored) |
4. Implementation Guidelines by Module
4.1 Authentication
- JWT
accessToken(15 min TTL) +refreshToken(7 days, stored in Redis). - OTP login via Resend; Google OAuth via
OAuthService.js. - Always use the
authenticatemiddleware on protected routes. - Role checks via
requireRoles('PLATFORM_ADMIN').
4.2 File Uploads
- Uploads go to Cloudinary via signed URL (never direct from client without signature).
- After upload, trigger the
moderationservice asynchronously (fire-and-forget). - Maximum sizes: 10 MB for product images; 5 MB for KYC documents.
- Accepted MIME types validated by
multerbefore any Cloudinary call.
4.3 Payments
- All payment orchestration goes through
PaymentService(the only module the rest of the app imports). - Use
PAYMENT_PROVIDER=MOCKin local development — no real gateway calls. - Mobile money: always validate Zambian phone format before calling the gateway.
- Card: always use
type: 'redirect'response — never collect card fields.
4.4 Multi-Vendor Order Handling
- Each
OrderItemis linked to aShopand carries its ownsettlementStatus. - The
SettlementLedgercalculatesvendorAmountandplatformFeeper item. - Only an admin action (
POST /payments/release/:orderId) transitionsHELD → RELEASABLE.
4.5 SEO
- Dynamic
<title>and<meta name="description">via Next.jsgenerateMetadata()on every page. - Structured data (JSON-LD) on product detail pages.
sitemap.xmlgenerated programmatically; submitted to Google Search Console.
5. Git Workflow
| Branch | Purpose | Merge policy |
|---|---|---|
feature/* | New features | PR → main; requires 1 review + CI pass |
hotfix/* | Critical production fixes | PR → production; fast-track review |
chore/* | Dependency updates, docs | PR → main |
main | Integration / staging | Protected; no direct push |
production | Live platform | Protected; triggers EC2 deploy via GitHub Actions |
Commit message format:
type(scope): short description
Types: feat, fix, chore, docs, refactor, test, style
Example: feat(payments): add PawaPay USSD overlay component