A High-Performance, Production-Grade REST API for Network Asset Management
- Executive Summary
- Quick Snapshot (Cheat Sheet)
- System Model & Architecture
- Key Features
- Technology Stack
- Configuration & Environment
- Repository Map (Quick View)
- Getting Started
- Quick Reference
- API Playbook (cURL)
- HTTP Lifecycle Visualization (Frontend)
- Postman API Guide
- Tutorial: Full Lifecycle
- Security & Validation
- Postman Pro Tips
- Testing & Quality
- Troubleshooting
- Contributing
- License
This project creates a scalable back-end infrastructure for managing complex network inventories. Designed with Golang, it prioritizes low latency, concurrency, and maintainability. It serves as a single source of truth for physical assets (Locations), network devices (Switches/Routers), and logical configurations (VLANs/Interfaces).
Ideally suited for:
- ISPs & Telecoms: Tracking thousands of devices.
- Enterprise IT: Managing campus networks.
- Data Centers: Automating infrastructure documentation.
- Run:
make start(Docker) ormake run(local Postgres). - Login:
POST /users/login→ keep the token inAuthorization: Bearer <token>; cookie only persists over HTTPS. - Base URL:
http://localhost:3000(versionless). - Seed users (password
adminfor all):admin,editor,viewer. - UI & animation:
http://localhost→ open HTTP Lifecycle from the Docs link. - Data scale: seed now ships 500 devices / ~1000 interfaces, 200 VLANs; every list supports
page/limit+ multisortby.
API runs on HTTP :3000 by default; TLS is optional (make gen-certs + ListenAndServeTLS). Frontend and docs are served by Nginx on :80.
graph TB
classDef client fill:#f9f9f9,stroke:#333,stroke-width:2px,rx:5;
classDef mid fill:#e1f5fe,stroke:#0288d1,stroke-width:2px,rx:5;
classDef app fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,rx:5;
classDef data fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,rx:5;
subgraph Clients
Web["Frontend (Nginx :80)"]:::client
Postman["Postman / curl"]:::client
end
subgraph "API :3000 (HTTP, TLS-ready)"
CORS["CORS allow-list<br/>(localhost/127.0.0.1 dev ports)"]:::mid
subgraph "Middleware Stack"
RealIP["RealIP + RequestID"]:::mid
Rate["RateLimit 100/min"]:::mid
HPP["HPP (allow: sortby, tags)"]:::mid
SecHdr["SecurityHeaders + CSP/HSTS"]:::mid
Sanitize["bluemonday sanitize"]:::mid
FetchMD["Fetch-Metadata CSRF guard"]:::mid
Logger["Logger + Recovery + X-Response-Time"]:::mid
Gzip["Compression (gzip)"]:::mid
end
Router["Gorilla Mux"]:::app
subgraph Controllers
Auth["Users/Auth"]:::app
Dev["Devices"]:::app
Ifc["Interfaces"]:::app
Loc["Locations"]:::app
Vlan["VLANs"]:::app
Link["Links"]:::app
Event["Events"]:::app
Metric["Metrics"]:::app
Audit["Audit Logs"]:::app
Debug["/debug/count"]:::app
end
end
subgraph Persistence
DB[("PostgreSQL 15<br/>schema.sql + seed.sql")]:::data
Mail["MailHog SMTP (1025/8025)"]:::data
end
Web -->|XHR /devices| CORS
Postman --> CORS
CORS --> RealIP --> Rate --> HPP --> SecHdr --> Sanitize --> FetchMD --> Logger --> Gzip --> Router
Router --> Auth & Dev & Ifc & Loc & Vlan & Link & Event & Metric & Audit & Debug
Auth -.-> Mail
Auth --> DB
Dev & Ifc & Loc & Vlan & Link & Event & Metric & Audit --> DB
- Blazing Fast: Native Go, Gorilla Mux, gzip by default.
- Security Stack (HTTP default, TLS optional):
- JWT + Argon2id; all protected routes behind
AuthMiddleware. - RealIP, RequestID, rate limit (100 req/min), HPP allow-list (
sortby,tags). - SecurityHeaders (CSP, HSTS, COOP/COEP/CORP, X-Frame-Options), Fetch-Metadata, strict bluemonday sanitization.
- CORS allow-list for localhost/127.0.0.1 dev ports; credentials supported.
- Logger + panic recovery +
X-Response-Timeheader.
- JWT + Argon2id; all protected routes behind
- Docker Native: Compose brings API + Postgres + PgAdmin + MailHog + static frontend.
- Seeded at Scale:
migrations/seed.sqlloads 10k devices / 20k interfaces. - Advanced Filtering & Sorting: Filters + multi
sortbyon every list endpoint. - Pagination:
page/limit(max 100) with meta + data wrapper. - Audit & Metrics: Audit log (admin), metrics endpoints for latest device stats.
All list endpoints (GET /devices, GET /users, etc.) support pagination to handle large datasets efficiently.
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int |
1 |
The page number to retrieve. |
limit |
int |
10 |
The number of items per page (Max: 100). |
Example:
GET http://localhost:3000/devices?page=2&limit=5
The response for list endpoints is wrapped in a standard structure containing metadata and the data array.
{
"meta": {
"current_page": 2,
"limit": 5,
"total_pages": 4,
"total_count": 20
},
"data": [
{ ... },
{ ... }
]
}| Component | Technology | Description |
|---|---|---|
| Language | Go (Golang) | 1.21+ for high concurrency and performance. |
| Database | PostgreSQL | Relational data integrity (UUIDs, Foreign Keys). |
| Routing | Gorilla Mux | Robust request routing. |
| Container | Docker | Standardized deployment environment. |
| Management | PgAdmin 4 | Web-based database management GUI. |
- ENV (.env):
POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB,PGADMIN_*. API also readsDB_*,JWT_SECRET,MAIL_*(seeinternal/config/config.go). - Ports: API 3000, Frontend 80, PgAdmin 5050, MailHog 1025/8025.
- TLS: Default is HTTP. Generate certs via
make gen-certs; then run withListenAndServeTLS(self-signed in dev). - JWT: Secret + expiry from env (
JWT_SECRET,JWT_EXPIRATION, default 24h). Cookie isSecure=true→ browsers ignore it on plain HTTP. - Seed Data:
migrations/seed.sqlloads 24 users, 17 locations, 500 devices, ~1000 interfaces, 200 VLANs, links, event/metric samples. - RBAC: Mutations require admin; links allow admin|editor; user routes allow owner or admin.
cmd/api/main.go— config load, DB existence check, migrations, server bootstrap.internal/router/*.go— resource-based routes + middleware stack.internal/api/handlers/*— HTTP handlers: validation, repository calls, JSON responses.internal/repositories/sqlconnect/*— SQL builder, CRUD, filtering/sorting/paging.internal/api/middlewares/*— Auth, RateLimit, HPP, SecurityHeaders, Fetch-Metadata, bluemonday sanitize, CORS.internal/models/*— data models +Validate()rules.pkg/utils/*— JWT, Argon2id, pagination, errors, random code.migrations/schema.sql&migrations/seed.sql— schema + demo data.frontend/— Nginx-served UI;frontend/docs/http-lifecycle-animation.htmlvisualization.
- Docker Desktop (Required)
- Make (Recommended for automation)
- Go 1.21+ (Only for local dev without Docker)
- cURL or Postman (For testing)
The project includes a robust Makefile for automation. To start the entire stack (Database + API + Admin GUI) and populate it with seed data:
make startThis will:
- Start Postgres, PgAdmin, MailHog, API, and frontend containers.
- Wait for the Postgres healthcheck.
- Apply
migrations/schema.sql(API also checks on startup). - Load
migrations/seed.sql(users, locations, 10k devices, 20k interfaces, VLANs, links). - Services exposed:
- API:
http://localhost:3000(HTTP; enableListenAndServeTLSfor HTTPS) - Frontend + docs:
http://localhost - PgAdmin:
http://localhost:5050 - MailHog UI:
http://localhost:8025
- API:
make build # Compile the binary to /bin
make run # Run locally (requires external DB)
make clean # Remove build artifacts
make up # Start Docker containers only
make down # Stop all containers
make reset_db # The "Nuclear Option": Wipes DB volume and re-seeds data
make gen-certs # Generate new SSL certificatesThe project includes a robust Makefile for advanced automation. Here is how to use it like a pro:
| Command | Description |
|---|---|
make up |
Start Everything: Clean start of API + DB + MailHog + PgAdmin in background. |
make down |
Stop Everything: Stops all containers and removes networks (safe shutdown). |
make restart |
Reboot: Sequentially runs down then up. Useful for quick resets. |
make logs |
Live Logs: tails the logs of all services in real-time. |
make shell |
Terminal Access: Opens a shell inside the running API container for debugging. |
make build |
Compile: Builds the Go binary locally to /bin (no Docker required). |
make run |
Dev Mode: Runs go run main.go locally (requires local Postgres). |
make test |
Test Suite: Runs all unit/integration tests with verbose output. |
make fmt |
Code Style: Formats all Go files using standard go fmt. |
make vet |
Linting: Runs static analysis to find potential bugs (go vet). |
make tidy |
Dependencies: Cleans up go.mod (removes unused libs, adds missing ones). |
make docker-build |
Force Build: Re-builds the Docker image from scratch (pulls latest Alpine/Go). |
make reset_db |
The "Nuclear" Option: |
make gen-certs |
SSL/TLS: Generates fresh self-signed certificates for local HTTPS. |
We provide three distinct ways to run this application, depending on your needs:
Best for: Production simulation, ease of use, zero dependency hell.
- How:
make up - Why: Runs everything (Postgres, MailHog, PgAdmin, API) in isolated containers. No local Go or Postgres installation required.
- Access: API
http://localhost:3000, frontend/docshttp://localhost.
Best for: Rapid coding, debugging, and testing changes instantly.
- Prerequisite: You need a local PostgreSQL instance running or use
docker-compose up postgres -d. - How:
make run(orgo run cmd/api/main.go) - Why: Fastest feedback loop. Uses your machine's resources directly.
Best for: High-performance deployment on bare metal.
- How:
make build(Generatesbin/api.exeorbin/api)./bin/api.exe
- Why: Runs the optimized, compiled binary. This is how the app runs in real production environments.
- Base URL:
http://localhost:3000(versionless). Enable TLS if needed. - Frontend/docs:
http://localhost(Nginx). - Seed Users (password
adminfor all, seemigrations/seed.sql):admin(role: admin)editor(role: editor)viewer(role: viewer)
- Auth header:
Authorization: Bearer <token>(cookie isSecure=true; on HTTP always send the header). - ID Format: UUID (e.g.,
550e8400-e29b-41d4-a716-446655440000).
curl -X POST http://localhost:3000/users/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}'- List (filters + multi-sort):
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/devices?vendor=Cisco&status=active&sortby=hostname:asc&sortby=last_seen:desc&page=1&limit=20"- Create:
curl -X POST http://localhost:3000/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hostname":"nyc-core-01","ip":"10.0.0.1","model":"ASR 9000","vendor":"Cisco","os":"IOS-XR","status":"active","location_id":"<LOC_UUID>"}'- Partial update (PATCH):
curl -X PATCH http://localhost:3000/devices/<DEV_UUID> \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"maintenance","role":"core"}'curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/interfaces?speed=400Gbps&status=up&sortby=name:asc"curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/locations?country=Turkey&sortby=city:asc"curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/vlans?vlan_id=100&sortby=name:asc"curl -X POST http://localhost:3000/links \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"a_interface_id":"<IF_A>","b_interface_id":"<IF_B>","type":"fiber","status":"up"}'curl -H "Authorization: Bearer $TOKEN" "http://localhost:3000/users?page=1&limit=20"- Open the interactive flow at
http://localhost/docs/http-lifecycle-animation.htmlaftermake up. - Stage data:
frontend/docs/js/data.js; player logic:frontend/docs/js/app.js. - Keep it in sync with the API:
- Paths are versionless:
/devices,/users/login,/interfaces, etc. Base:http://localhost:3000. - Default transport is HTTP; update TLS notes if you enable HTTPS.
- Middleware order: CORS -> RealIP -> RequestID -> RateLimit -> HPP (
sortby,tags) -> SecurityHeaders -> Sanitize -> FetchMetadata -> Logger -> Compression -> Router -> Handler -> PostgreSQL.
- Paths are versionless:
- Avoid inline scripts in stage HTML; the viewer sanitizes content before rendering.
Login (Obtain Token & Cookie)
POST /users/login
Content-Type: application/json
{
"username": "admin",
"password": "admin"
}Use the token from the response body in the
Authorization: Bearer <token>header. The cookie isSecure=true, so browsers ignore it on plain HTTP—use HTTPS or stick to the header.
Logout (Clear Session)
POST /users/logoutUpdate Password
PUT /users/{UUID}/password
Authorization: Bearer <token>
Content-Type: application/json
{
"new_password": "secure_pass_123"
}Forgot Password
POST /users/forgot-password
Content-Type: application/json
{
"email": "admin@example.com"
}List All Devices
GET /devicesFilter by Vendor & Status
GET /devices?vendor=Cisco&status=activeAdvanced Sort (Multi-Field)
GET /devices?sortby=vendor:asc&sortby=hostname:descCreate New Device
POST /devices
Content-Type: application/json
{
"hostname": "nyc-core-01",
"ip": "10.0.0.1",
"model": "ASR 9000",
"vendor": "Cisco",
"os": "IOS-XR",
"status": "active",
"location_id": "uuid-goes-here"
}Bulk Delete Devices
DELETE /devices
Authorization: Bearer <token>
Content-Type: application/json
["uuid-1", "uuid-2", "uuid-3"]| Parameter | Description | Example |
|---|---|---|
hostname |
Exact match | ?hostname=core-router-01 |
ip |
Exact IP match | ?ip=10.20.30.40 |
vendor |
Manufacturer | ?vendor=Cisco |
model |
Hardware model | ?model=ASR9000 |
os |
Operating System | ?os=IOS-XR |
os_version |
OS Version string | ?os_version=7.4.1 |
status |
Lifecycle status | ?status=active |
role |
Functional role | ?role=spine |
serial_number |
Serial Number | ?serial_number=SN12345 |
rack_position |
Location details | ?rack_position=Rack-A01 |
location_id |
UUID of location | ?location_id={UUID} |
last_seen |
Timestamp | ?last_seen=2024-01-01T00:00:00Z |
| Requirement | Method | URL Path |
|---|---|---|
| Get Device Interfaces | GET |
/devices/{id}/interfaces |
| Quick Interface Count | GET |
/devices/{id}/interfacecount |
| Partial Update (Bulk) | PATCH |
/devices (Batch Array) |
Find High-Speed Links
GET /interfaces?speed=100Gbps&status=upSearch Optical Links
GET /interfaces?type=fiber&sortby=name:ascBulk Update (Shutdown)
PATCH /interfaces
Content-Type: application/json
[
{ "id": "uuid-1", "status": "down" },
{ "id": "uuid-2", "status": "down" }
]Bulk Delete Interfaces
DELETE /interfaces
Authorization: Bearer <token>
Content-Type: application/json
["uuid-1", "uuid-2"]| Parameter | Description | Example |
|---|---|---|
name |
Interface name | ?name=Eth1/1 |
type |
Media type | ?type=fiber |
status |
Link status | ?status=up |
admin_status |
Configured status | ?admin_status=up |
oper_status |
Protocol status | ?oper_status=down |
speed |
Human-readable speed | ?speed=10Gbps |
speed_mbps |
Speed in Mbps (Int) | ?speed_mbps=10000 |
mtu |
Packet size | ?mtu=9000 |
mac_address |
Hardware address | ?mac_address=00:aa:bb... |
mode |
L2/L3 Mode | ?mode=trunk |
ifindex |
SNMP Index | ?ifindex=101 |
device_id |
Parent Device UUID | ?device_id={UUID} |
ip_address |
Assigned IP | ?ip_address=192.168.1.1 |
| Requirement | Method | URL Path |
|---|---|---|
| Batch Status Update | PATCH |
/interfaces (Bulk Patch) |
List Locations by Country
GET /locations?country=TurkeyRegister New Site
POST /locations
Content-Type: application/json
{
"name": "Istanbul DC",
"city": "Istanbul",
"country": "Turkey",
"address": "Maslak 1453"
}| Requirement | Method | URL Path |
|---|---|---|
| Get Site Inventory | GET |
/locations/{id}/devices |
| Quick Device Count | GET |
/locations/{id}/devicescount |
| Batch Update Sites | PATCH |
/locations (Bulk Patch) |
| Batch Remove Sites | DELETE |
/locations (Array of IDs) |
Filter by VLAN ID
GET /vlans?vlan_id=100Search by Name
GET /vlans?name=Voice&status=activeCreate VLAN
POST /vlans
Content-Type: application/json
{
"vlan_id": 20,
"name": "VoIP-Users",
"description": "Voice over IP Segment"
}| Requirement | Method | URL Path |
|---|---|---|
| Batch Update VLANs | PATCH |
/vlans (Bulk Patch) |
| Batch Remove VLANs | DELETE |
/vlans (Array of IDs) |
Topology Management (Links) Full CRUD support for physical link management between interfaces.
- List All Links:
GET /links - Create Link:
POST /links Content-Type: application/json { "a_interface_id": "{UUID}", "b_interface_id": "{UUID}", "type": "fiber", "status": "up" }
- Update Link:
PUT /links/{id} - Delete Link:
DELETE /links/{id}
System Events
- List Events:
GET /events - Create Event (System/Webhooks):
POST /events Content-Type: application/json { "severity": "critical", "type": "security", "message": "Unauthorized access attempt detected", "device_id": "{OPTIONAL_UUID}" }
Audit Logs (Admin Only) Track all critical system modifications.
- List Logs:
GET /audit-logs?page=1&limit=20 Authorization: Bearer <admin_token>
Performance Metrics
- System Overview:
GET /metrics?sort=value:desc Authorization: Bearer <token>
- Device Specific:
GET /metrics/device/{id} Authorization: Bearer <token>
Utilities & Debug
- Debug Location Count (Public Utility):
Response:
GET /debug/count
{ "count": 15 }
Verifies that the API server is reachable.
GET /Response: "Running API v1"
Fetch page 2 with 5 items per page.
GET /devices?page=2&limit=5401 Unauthorized (Missing Token)
GET /usersResponse:
{ "status": "error", "message": "Unauthorized: No security token provided" }404 Not Found (Invalid UUID)
GET /devices/00000000-0000-0000-0000-000000000000Response:
{ "status": "error", "message": "Device not found" }All errors follow a unified JSON structure for easy parsing by frontend clients:
{
"status": "error",
"message": "Human readable reason for failure"
}| Status Code | Meaning |
|---|---|
| 400 | Bad Request: Validation failed or missing fields. |
| 401 | Unauthorized: Invalid or missing JWT/Cookie. |
| 403 | Forbidden: RBAC restriction or cross-site attack. |
| 404 | Not Found: Resource does not exist. |
| 429 | Too Many Requests: Rate limit exceeded. |
| 500 | Server Error: Database or internal logic failure. |
400 Bad Request (Validation Failure)
POST /devices
Content-Type: application/json
{ "ip": "999.999.999.999" }Response:
{ "status": "error", "message": "Invalid IP address format" }403 Forbidden (Insufficent Permissions)
DELETE /locations/uuidResponse:
{ "status": "error", "message": "Forbidden: You do not have permission to perform this action" }409 Conflict (Duplicate Resource)
POST /usersResponse:
{ "status": "error", "message": "Email already exists" }429 Too Many Requests (Rate Limit Exceeded) Response (includes Retry-After header):
{ "status": "error", "message": "Too many requests" }500 Internal Server Error (Unexpected Failure) Response:
{ "status": "error", "message": "Internal Server Error" }HPP Blocking (Default) Trying to confuse the server with duplicate parameters.
GET /devices?page=1&page=500Response:
{ "status": "error", "message": "Duplicate query parameter forbidden: page" }HPP Allowed List (e.g. Sorting)
Specific parameters like sortby are whitelisted for multi-value usage.
GET /devices?sortby=model:asc&sortby=vendor:descResponse: Returns devices sorted first by model, then by vendor.
Locate by Serial Number
GET /devices?serial_number=SN-99887766Find Devices in Specific Rack
GET /devices?rack_position=Rack-42-U10Search Interfaces by MAC Address Trace the physical location of a connected client.
GET /interfaces?mac_address=00:1A:2B:3C:4D:5E| Requirement | Method | Full Request URL Path |
|---|---|---|
| Simple Listing | GET |
/devices |
| Get Specific User Settings | GET |
/users/{UUID} |
| Newest Inventory First | GET |
/devices?sortby=created_at:desc |
| Backbone Interface Sort | GET |
/interfaces?speed=400Gbps&sortby=name:asc |
| Down Status (Fiber Only) | GET |
/interfaces?type=fiber&status=down&sortby=speed:desc&sortby=name:asc |
| Inactive Accounts Search | GET |
/users?inactive_status=true |
| Turkey Hub Multi-Sort | GET |
/locations?country=Turkey&sortby=city:asc&sortby=name:desc |
| FinTech VLANs (High ID First) | GET |
/vlans?name=HFT&sortby=vlan_id:desc |
| Bulk Interface Shutdown | PATCH |
/interfaces (With JSON Body Array) |
| Delete Location (keeps devices) | DELETE |
/locations/{UUID} (location_id set to NULL; devices remain) |
Follow these scenarios to master every method available in the API. Each scenario walks you through a real-world network engineering workflow.
Tip
This workflow demonstrates how to provision, monitor, and decommission a core network asset.
POST (Create): Register a new Nokia Service Router.
POST /devices{
"hostname": "br-ist-core-01",
"ip": "100.100.100.1",
"model": "7750 SR-1s",
"vendor": "Nokia",
"os": "SR OS",
"status": "active",
"location_id": "{LOC_UUID}"
}GET (Verify): List all Nokia devices to find your new router.
GET /devices?vendor=Nokia&sortby=hostname:ascPATCH (Update): Set the router to maintenance mode for a firmware upgrade.
PATCH /devices/{UUID}{"status": "maintenance"}PUT (Full Replace): Transition to a new hardware model and IP while keeping the same UUID.
PUT /devices/{UUID}{
"hostname": "br-ist-core-01-v2",
"ip": "100.100.100.2",
"model": "7750 SR-12",
"vendor": "Nokia",
"os": "SR OS v22",
"status": "active"
}DELETE (Remove): Decommission the legacy router.
DELETE /devices/{UUID}Note
Managing physical connectivity at scale using bulk operations and granular filtering.
POST (Provision): Create the primary 400G optical port.
POST /interfaces{
"device_id": "{DEV_UUID}",
"name": "400GE1/1/1",
"description": "Inter-AS Link",
"type": "fiber",
"speed": "400Gbps",
"status": "up"
}GET (Audit): Search for all terabit-scale interfaces.
GET /interfaces?speed=400Gbps&sortby=status:ascPATCH (Bulk): Shutdown multiple backbone links for emergency fiber repair.
PATCH /interfaces[
{ "id": "{IF_UUID_1}", "status": "down" },
{ "id": "{IF_UUID_2}", "status": "down" }
]DELETE: Clean up an incorrectly provisioned test port.
DELETE /interfaces/{UUID}Important
Logical asset management for high-frequency trading segments.
POST: Define a new ultra-low latency multicast VLAN.
POST /vlans{
"vlan_id": 105,
"name": "NY4-Mcast-Feed",
"description": "NYSE Data Feed"
}GET: Verify the VLAN exists and check its sort position.
GET /vlans?sortby=vlan_id:asc&name=NY4PATCH: Update description to include specific sub-feeds.
PATCH /vlans/{UUID}{"description": "NYSE Data Feed (Equinix NY4 Hub)"}DELETE: Remove VLAN after the market data session ends.
DELETE /vlans/{UUID}This project implements industry-standard security practices and strict data validation to ensure network integrity.
- Transport: API on HTTP :3000. HSTS header is set; use
ListenAndServeTLS+make gen-certsif you need HTTPS. - CORS: Allow-list (localhost/127.0.0.1 dev ports, https://localhost:3000). Unknown origins get 403.
- Rate Limiting: 100 req/min per IP, with
X-RateLimit-*+Retry-After. - HPP: Blocks duplicate query params except
sortbyandtags. - Sanitization: bluemonday (strict), 1MB body cap; rejects if sanitization changes input.
- Fetch Metadata: Modern CSRF guard; blocks cross-site state-changing requests.
- Security Headers: CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Permissions-Policy (camera/mic/geo off), COOP/COEP/CORP, Referrer-Policy: strict-origin-when-cross-origin, HSTS.
- Logging & Observability: Real IP + Request ID, panic recovery,
X-Response-Time. - RBAC: Mutations via
RequireRole("admin"); links allowadmin|editor; user routes useRequireOwnerOrAdmin. - Auth Tokens: JWT secret from env; cookie
HttpOnly,SameSite=Strict,Secure=true. On HTTP, always send the header.
The API implements a robust RBAC system to ensure data security. Roles are embedded in the JWT and checked via middleware.
| Role | Access Level | Description |
|---|---|---|
| Admin | Full Access | Can create, update, delete, and list all resources. |
| User | Read-Only / Limited | Can list resources (Devices, Locations) but cannot modify them. |
| Owner | Resource-Specific | Can only update/delete resources they own (e.g., their own user profile). |
Important
Owner vs Admin: If a user is an admin, they can modify any resource. If a user is a user, they can only modify their own data (e.g., PUT /users/{MY_UUID}) if they are the Owner.
Middleware Usage Example:
// In router.go
adminOnly := middlewares.RequireRole("admin")
protected.Handle("/devices", adminOnly(http.HandlerFunc(handlers.CreateDevice))).Methods("POST")To prevent Cross-Site Scripting (XSS) attacks, the API automatically sanitizes all incoming JSON and Form bodies using the bluemonday UGC policy.
- Strict Mode: Rejects requests (400 Bad Request) if suspicious HTML/JS is detected.
- Recursive Cleaning: Deep-sanitizes nested JSON objects and arrays.
- Query Param Safety: Automatically cleans search parameters to prevent injection via URL.
For developers, the internal/utils/sql_builder.go provides a type-safe way to generate dynamic SQL queries.
GenerateInsertQuery: Automatically maps Go structs to SQL INSERT statements based ondborjsontags.BuildUpdateQuery: Constructs dynamic UPDATE statements with allowed-field filtering.GetStructValues: Reflectively extracts data from structs for batch operations.
Developer Example:
data, _ := utils.GetStructValues(device)
query, args, _ := utils.GenerateInsertQuery("devices", data)
// Result: INSERT INTO devices (hostname, ip...) VALUES ($1, $2...) RETURNING idThe API uses secure, server-side cookies for session management. When you log in via /users/login, the server issues a Bearer cookie with:
- HttpOnly (No JS access)
- Secure (HTTPS only)
- SameSite=Strict (CSRF protection)
- MaxAge=24h
These examples demonstrate how the API handles invalid inputs.
- Invalid IP Format:
POST {{base_url}}/deviceswith{"ip": "999.999.999.999"}->400 Bad Request - VLAN Range Check:
POST {{base_url}}/vlanswith{"vlan_id": 5000}->400 Bad Request - Missing Required Fields:
POST {{base_url}}/userswith empty fields ->400 Bad Request
A comprehensive security and code quality audit was performed to ensure production readiness.
- Upgrade: Refactored
Argon2verification logic to dynamically parse hash parameters (Memory, Time, Threads). - Benefit: Ensures forward compatibility. If security parameters are increased in the future, existing user passwords will remain valid without migration.
- Verification: New unit tests added in
pkg/utils/password_test.go.
- Backend Audit: Verified all 50+ API endpoints for consistency between Go handlers (Backend) and JS Client (Frontend).
- Clean Code: Removed debug artifacts (
console.log) from production frontend builds. - Stability: Confirmed zero-warning build status with
go buildandgo vet.
The API utilizes a modern, hybrid JWT (JSON Web Token) authentication system designed for both programmatic clients (CLI/Mobile) and Web Browsers.
We implement a Dual-Delivery mechanism for the authentication token:
-
The Token (Bearer):
- What is it?: A signed JWT containing the User ID, Username, and Role.
- Use Case: Mobile Apps, CLIs, Postman, and external scripts.
- How to use: Send it in the Header:
Authorization: Bearer <your_token>.
-
The Cookie (HttpOnly):
- What is it?: The exact same JWT, stored in a secure, server-side Cookie.
- Use Case: Web Applications (React, Vue, etc.).
- Security: The cookie is
HttpOnly(JavaScript cannot read it),Secure(HTTPS only), andSameSite=Strict(Prevents CSRF). - How to use: The browser automatically handles this. You don't need to do anything manually!
Request:
POST /users/login
Content-Type: application/json
{
"username": "admin",
"password": "your_secure_password"
}Response (Success - 200 OK):
{
"status": "success",
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Note: The response also sets the Bearer cookie automatically.
Terminates the server-side session by invalidating the cookie.
Request:
POST /users/logoutResponse (Success - 200 OK):
{
"status": "success",
"message": "Logged out"
}Note: This action clears the Bearer cookie by setting its expiration date to the past.
Level up your API testing by using Postman's built-in automation features.
Instead of hardcoding URLs, use environments (the eye icon top-right).
- Variable:
base_url->http://localhost:3000 - Variable:
bearer_token-> (leave empty, will be auto-filled)
Add this to the Tests tab of your POST /users/login request. It will automatically save the token for all future requests.
// Parse response
const response = pm.response.json();
// Save token to environment if login is successful
if (pm.response.code === 200 && response.token) {
pm.environment.set("bearer_token", response.token);
console.log("Token auto-saved to environment!");
}
// Pro-Tip: Add a test to verify status
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});Add these to the Tests tab of any GET request to ensure the API stays healthy.
// 1. Verify response time (Good for HFT testing)
pm.test("Response time is less than 200ms", function () {
pm.expect(pm.response.responseTime).to.be.below(200);
});
// 2. Check for required fields
pm.test("Response has required fields", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.be.an('array');
if (jsonData.length > 0) {
pm.expect(jsonData[0]).to.have.property('id');
pm.expect(jsonData[0]).to.have.property('hostname');
}
});Generate random data for stress testing in the Pre-request Script tab.
// Generate a unique hostname
const randomSuffix = Math.floor(Math.random() * 1000);
pm.variables.set("dynamic_hostname", "rtr-automated-" + randomSuffix);Now use {{dynamic_hostname}} in your POST body!
- SSL Verification: Disable only if you enable HTTPS with self-signed certs; default setup is HTTP.
- Auth (Next Step): Copy the
tokenfrom the login response. In other requests, go to theAuthtab, selectBearer Token, and paste it.[!TIP] If you used the Auto-Save Token script above, just type
{{bearer_token}}in the Token field! - Collection Variables:
base_url = http://localhost:3000. Use{{base_url}}/devices.
- Unit tests:
go test ./... -v - Static analysis:
make vet - Format:
make fmt - Build sanity:
make build - Watch logs:
make logs(API, Postgres, MailHog together)
- 401 / 403: Missing/expired token or insufficient role (mutations require admin; links admin|editor).
- CORS 403: Origin not in allow-list. Check the
Originheader. - Cookie missing:
Secure=true; browsers drop it on HTTP. Send the token in the header or enable HTTPS. - Duplicate query param error: HPP allows multi-values only for
sortbyandtags. - Self-signed HTTPS: After
make gen-certs, disable SSL verification in Postman or usecurl -k.
We welcome contributions! Please follow the standard "Fork & Pull Request" workflow.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.
Distributed under the MIT License. See LICENSE for more information.