Documentation
Complete guide to PatrolLink architecture, API, and deployment.
Project Summary
PatrolLink is a comprehensive security management platform that enables organizations to manage their security guards, track patrols in real time, and maintain operational oversight through an intuitive dashboard. The system consists of a cross-platform mobile app for guards, a web-based admin dashboard, and a robust RESTful API backend.
The platform solves the challenge of managing distributed security teams by providing real-time GPS tracking, structured patrol logging, incident reporting, and role-based access control — all from a single integrated system.
Core Capabilities
- Real-time guard tracking — GPS-based patrol monitoring with live location updates
- Patrol lifecycle management — Start, monitor, and complete patrol sessions with checkpoint logging
- Incident reporting — Guards can log incidents with photos, GPS coordinates, and notes
- Role-based access — Distinct views and permissions for guards, supervisors, and administrators
- Shift management — Assign guards to locations, set operating hours, and track coverage
- Push notifications — Real-time alerts for incidents, patrol status changes, and administrative updates
Technology Stack
React Native + Expo
Cross-platform mobile app with GPS, camera, and push notifications.
Express.js
Node.js backend providing RESTful API, session management, and middleware.
PostgreSQL
Relational database for users, patrols, assignments, logs, and sessions.
Directus
Headless CMS providing auto-generated REST API for content management.
JWT + bcryptjs
Token-based auth with password hashing for secure access control.
EJS
Server-side templating for web views and documentation.
Technology Justification
| Technology | Justification |
|---|---|
| React Native + Expo | Single codebase for iOS and Android; built-in GPS, camera, and notification APIs. |
| Express.js | Minimal overhead, flexible middleware, well-suited for REST APIs. |
| PostgreSQL | ACID-compliant, excellent with geospatial data, reliable for security data. |
| Directus | Headless CMS auto-generates REST APIs; provides admin UI without building CRUD views. |
| JWT | Stateless authentication, works well with mobile clients. |
| EJS | Simple server-side rendering; no client framework needed for docs and auth pages. |
Why PatrolLink?
- Accountability — GPS-verified patrols ensure guards complete assigned routes
- Efficiency — Supervisors monitor all guards from a single dashboard
- Transparency — Complete audit trail of patrols, logs, and incidents
- Scalability — From single site to multi-location enterprise deployments
- Integration — RESTful API enables integration with existing security infrastructure
Frontend Architecture
The PatrolLink frontend consists of two distinct surfaces: the mobile app for guards and server-rendered web views for documentation and authentication.
Mobile App (React Native + Expo)
The guard-facing mobile application is built with React Native using the Expo managed workflow. This provides cross-platform compatibility (iOS and Android) with access to native device features:
- expo-location — GPS tracking for patrol routes and location check-ins
- expo-camera — Photo capture for incident reports and checkpoint evidence
- expo-notifications — Push notification support for real-time alerts
- expo-secure-store — Secure token storage for JWT authentication
- React Context API — State management for user sessions and patrol state
The app uses Expo Router for file-based navigation and communicates with the backend via fetch() calls to the Express.js API.
Web Views (EJS)
Documentation, authentication (login/signup), pricing, and the admin dashboard are server-rendered using EJS templates. This approach avoids the complexity of a separate frontend framework for pages that are primarily content or form-based. Templates are served directly by the Express server with inline CSS and minimal client-side JavaScript.
Express.js Server
The backend is built on Express.js 5 and serves as the central API gateway. It handles authentication, session management, routing, rate limiting, and communication with the database and Directus CMS.
Server Configuration
- Express 5 with middleware pipeline: helmet, cors, cookie-parser, body-parser, express-session
- View engine: EJS for server-rendered pages
- PostgreSQL connection via
pglibrary for sessions and direct queries - Directus REST API integration for user and content management
- Rate limiting on auth endpoints to prevent brute-force attacks
Authentication & Authorization
PatrolLink uses a dual authentication system:
- JWT tokens — Primary auth mechanism for mobile app API calls. Tokens are generated on login, include user role and organization data, and are verified on protected routes via the
verifyTokenMiddleware. - Express sessions — Used for web views (admin dashboard). Login creates a session with user data and a JWT token for server-to-API calls.
- Role-based access — The
requireRole()middleware restricts endpoints to specific roles (admin, supervisor).
Auth APIs
All API responses follow the format { "message": "...", "data": ... } for success or { "error": "...", "message": "..." } for errors.
| Parameter | Type | Required | Description |
|---|---|---|---|
| firstName | string | Yes | User's first name |
| lastName | string | Yes | User's last name |
| phone | string | Yes | Phone number (e.g., +254712345678) |
| password | string | Yes | Password (min 8 chars, must include uppercase, lowercase, digit, special char) |
| role | string | No | User role (default: "guard") |
| companyCode | string | No | Organization invite code |
// Example response (201 Created)
{
"message": "User registered successfully",
"user": { "id": "abc123", "name": "John Doe", "phone": "+254712345678", "role": "guard" }
}
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | Yes | Registered phone number |
| password | string | Yes | Account password |
// Example response (200 OK)
{
"message": "Login successful",
"user": { "id": "abc123", "role": "admin" },
"token": "eyJhbGciOiJIUzI1NiIs...",
"returnTo": "/admin/dashboard"
}
{ "message": "Logout successful" }
| Parameter | Type | Required | Description |
|---|---|---|---|
| token | string | Yes | JWT token to validate |
{ "valid": true, "user": { "id": "abc123", "role": "guard" } }
Guard APIs
Endpoints used by the mobile app for assignments, patrol lifecycle, location tracking, and logs.
{ "assignments": [ { "id": "asg1", "location": "loc1", "assigned_areas": "Gate A", "start_time": "09:00", "end_time": "17:00" } ] }
| Parameter | Type | Required | Description |
|---|---|---|---|
| location | string | Yes | Location ID |
| assigned_areas | string | Yes | Comma-separated areas |
| start_time | string | Yes | Shift start time |
| end_time | string | Yes | Shift end time |
?limit=10 and ?sort=-start_time.| Parameter | Type | Required | Description |
|---|---|---|---|
| start_time | string | Yes | Patrol start timestamp |
| user_id | string | Yes | Guard user ID |
| duration | number | No | Expected duration in minutes |
| location_data | array | No | Starting GPS coordinates |
end_time and status to complete.?limit=50 and ?sort=-timestamp.| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Log title |
| description | string | Yes | Log details |
| category | string | Yes | activity, unusual, incident, checkpoint, other |
| images | string|array | No | Image data or URIs |
| patrol_id | string | No | Related patrol ID |
Admin APIs
Endpoints for the admin dashboard to manage guards, assignments, locations, patrols, and logs. All scoped to the authenticated admin's organization via invite_code.
| Field | Type | Description |
|---|---|---|
| id | string | Guard user ID |
| first_name | string | Guard first name |
| last_name | string | Guard last name |
| phone | string | Phone number |
| location | string | Resolved location name |
| is_online | boolean | Currently on active patrol |
| last_seen_display | string | Online status or timestamp |
{ "message": "Guard removed successfully", "deleted": { "assignments": 2, "logs": 8, "patrols": 5 } }
| Parameter | Type | Required | Description |
|---|---|---|---|
| user_id | string | Yes | Guard user ID |
| location | string | Yes | Location ID |
| assigned_areas | string | Yes | Comma-separated areas |
| start_time | string | Yes | Start time (e.g., 09:00) |
| end_time | string | Yes | End time (e.g., 17:00) |
?limit=50 and ?sort=-start_time.?limit=50 and ?sort=-timestamp.| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Location name |
| assigned_areas | string | No | Comma-separated areas |
Additional APIs
{ "valid": true, "message": "Invite code is valid" }
Middleware
| Middleware | Purpose |
|---|---|
verifyTokenMiddleware | Validates JWT from Authorization: Bearer <token> header and attaches decoded user to req.user |
requireRole(...roles) | Restricts access to specified roles (e.g., requireRole('admin', 'supervisor')) |
requireAuth | Session-based auth guard for web views; redirects unauthenticated users to login |
loginLimiter | Rate limiter for login/register endpoints to prevent brute-force attacks |
patrolLimiter | Rate limiter for patrol creation to prevent spam |
logLimiter | Rate limiter for log creation |
checkpointLateLimiter | Rate limiter for late checkpoint reports |
Error Handling
All error responses follow a consistent format:
{ "error": "ErrorType", "message": "Human-readable description" }
| Status | Error | Description |
|---|---|---|
| 400 | Validation Error | Missing required fields or invalid data |
| 401 | Unauthorized | Invalid credentials or missing token |
| 403 | Forbidden | Insufficient role permissions |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Duplicate entry or conflicting state |
| 500 | Internal Server Error | Server-side failure |
Database — PostgreSQL
PatrolLink uses PostgreSQL as its primary database for storing sessions and executing direct queries. User and content data is managed through Directus, which also uses PostgreSQL as its backing store.
Schema Overview
The database schema is managed by Directus and includes custom collections for PatrolLink-specific data. The pg library is used for direct PostgreSQL queries, primarily for session storage and admin dashboard queries.
Key Tables / Collections
| Collection | Description |
|---|---|
users | User accounts with roles (guard, supervisor, admin), phone numbers, and organization codes |
organizations | Organization records with invite codes, subscription tiers, and rates |
assignments | Guard-to-location assignments with operating hours and areas |
locations | Site locations with assigned areas linked to organizations |
patrols | Patrol sessions with start/end times, status, and GPS route data |
logs | Guard log entries with categories, descriptions, and optional images |
notifications | Push notification records for admin alerts and guard updates |
session | Express session store for web authentication |
CMS — Directus
Directus serves as the headless CMS layer, providing auto-generated REST APIs for CRUD operations on all data collections. It eliminates the need to build custom admin interfaces for data management while offering a powerful SDK for programmatic access.
Configuration
- Directus is configured via
DIRECTUS_URLandDIRECTUS_TOKENenvironment variables - The server uses Directus's REST API for querying users, creating records, and managing content
- A custom
query()function wraps Directus API calls with consistent error handling - Directus provides the admin UI for manually managing users, content, and schema changes
Environment Variables
Create a .env file in the backend root with the following:
# Server
APIPORT=5000
NODE_ENV=development
# JWT Secret (change in production)
JWT_SECRET=your-super-secret-jwt-key
# Directus Configuration
DIRECTUS_URL=http://your-directus-instance.com
DIRECTUS_TOKEN=your-directus-static-token
# PostgreSQL
DB_USER=postgres
DB_HOST=localhost
DB_NAME=omniwatch
DB_PASSWORD=your-password
DB_PORT=5432
DB_SSL=false
# CORS Allowed Origins (optional, comma-separated)
CORS_ALLOWED_ORIGINS=http://localhost:5000,http://localhost:8081
Running the App
# Install dependencies
npm install
# Development mode with auto-reload
npm run dev
# Production mode with PM2
npm run prod
# Or start directly
npm start