TechTraverse's config-driven map ecosystem: a public map viewer (map-client), an admin panel (admin-app), and an ingest pipeline (ingest-service) — all backed by PostGIS, tipg (OGC API), and an internal React component library (@techtraverse/map-ui-lib).
- Config-Driven Architecture: Define map behavior, layers, and UI through declarative JSON configuration validated with Zod schemas
- Separation of Concerns: Keep the component library framework-agnostic and MapLibre-independent for maximum reusability
- OGC API Standards: Use tipg (OGC API Features/Tiles server) as the primary data backend
- Shareable State: Enable shareable URLs via query parameters using nuqs
- Developer Experience: Provide excellent TypeScript support with comprehensive type inference
- Internal Library (
@techtraverse/map-ui-lib): Pure UI components with no MapLibre dependencies - Controlled Components: All components are fully controlled via props and callbacks
- Tree-shakeable: Multiple entry points for optimal bundle sizes
- Storybook-First: All components developed with interactive Storybook stories
- Type-Safe: End-to-end type safety from config validation to component props
┌─────────────────────────────────────────────────────────────┐
│ JSON Config │
│ (map-config.ts) │
└─────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Zod Validation (map-ui-lib) │
│ • MapConfig, LayerConfig, BasemapConfig │
│ • Runtime validation + TypeScript inference │
└─────────────────────┬───────────────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ map-ui-lib │ │ map-client │
│ │ │ │
│ • UI Components │───▶│ • Zustand Stores │
│ • Data Hooks │ │ • MapLibre GL JS │
│ • Schemas │ │ • URL State (nuqs) │
└──────────────────┘ └──────────────────────┘
map-traverse/
├── docker-compose.yml # PostGIS + tipg + seed + admin-app
├── packages/
│ └── map-ui-lib/ # Internal component library (@techtraverse/map-ui-lib)
│ ├── src/
│ │ ├── components/ # LayerPanel, Legend, BasemapSwitcher, CollapsibleControl,
│ │ │ # CoordinateDisplay, SearchPanel, FeatureDetailPanel,
│ │ │ # FeatureTooltip, ExportButton, + admin editor components
│ │ ├── hooks/ # useOgcCollections, useOgcFeatures, useOgcQueryables,
│ │ │ # useOgcCollectionDetail, useCsvExport
│ │ ├── schemas/ # Zod config schemas
│ │ ├── types/ # TypeScript types
│ │ └── utils/ # OGC API fetch utilities, CQL2 builders
│ └── .storybook/ # Storybook configuration
│
├── apps/
│ ├── map-client/ # Public map application
│ │ └── src/
│ │ ├── config/ # Map configuration
│ │ ├── stores/ # Zustand state management
│ │ ├── hooks/ # URL sync hooks
│ │ └── components/ # Map containers
│ │
│ ├── admin-app/ # Map config admin panel
│ │ ├── src/ # React frontend (config wizard, version history)
│ │ └── server/ # Express API + PostgreSQL backend
│ │
│ └── ingest-service/ # GIS data ingest pipeline (PostGIS loader)
│
└── docker/ # Development infrastructure
└── seed/ # Natural Earth sample data
- React 18: UI framework (peer dependency)
- Zod: Schema validation and type inference
- TailwindCSS v4: Styling with
mapui:prefix to prevent conflicts - Vite: Build tool with library mode
- Storybook 8: Component development environment
- React 18: UI framework
- MapLibre GL JS: Map rendering engine (via react-map-gl)
- Zustand: Lightweight state management
- nuqs: Type-safe URL state synchronization
- TailwindCSS v4: Styling
- PostGIS: Spatial database
- tipg: OGC API Features/Tiles server
- Natural Earth: Sample geographic data (110m scale)
- Node.js >= 18
- pnpm >= 10
- Docker and Docker Compose
# Install dependencies
pnpm install
# Start backend services (PostGIS + tipg)
docker compose up -d
# Wait for seed to complete (~30 seconds)
docker logs -f techtraverse-seed
# Verify tipg is serving data
curl http://localhost:8001/collections# Run Storybook (component library)
pnpm storybook
# Run client app
pnpm dev:app
# Build library
pnpm build:lib
# Build client app
pnpm build:app| URL | Service |
|---|---|
| http://localhost:8000 | Gateway (all services on one port) |
| http://localhost:3000 | Map client (direct) |
| http://localhost:3001 | Admin app (direct) |
| http://localhost:8001 | tipg OGC API (direct) |
| URL | Service |
|---|---|
| http://localhost:5173 | Map client (Vite) |
| http://localhost:5174 | Admin UI (Vite) |
| http://localhost:6006 | Storybook |
| localhost:5432 | PostGIS |
A single nginx gateway exposes all services through one port. The map client is public; the admin app requires authentication for write operations.
Internet
|
[ Gateway ]
nginx :8000
|
┌──────────┬───────┴───────┬──────────┐
| | | |
/api/* /admin/* /ogc/* /*
| | | |
┌─────┴─────┐ | ┌────┴────┐ ┌──┴──────────┐
| admin-app | | | tipg | | map-client |
| Express |───┘ | OGC API | | nginx SPA |
| :3001 | | :8000 | | :80 |
└─────┬─────┘ └────┬────┘ └─────────────┘
| |
└────────┬────────────────┘
|
┌─────┴─────┐
| PostGIS |
| :5432 |
└───────────┘
| Path | Upstream | Auth | Purpose |
|---|---|---|---|
/ |
map-client | None | Public map viewer SPA |
/api/configs |
admin-app | GET: none, POST: session | List / create map configs |
/api/configs/:id |
admin-app | GET: none, PUT/DELETE: session | Read / update / delete a config |
/api/configs/:id/publish |
admin-app | POST: session | Publish / unpublish a config |
/api/sources |
admin-app | GET: none, POST: session | List / create OGC data sources |
/api/settings |
admin-app | GET: none, PUT: session | Site branding settings |
/api/auth/login |
admin-app | None | Session login |
/api/health |
admin-app | None | Health check |
/admin/* |
admin-app | Session (UI) | Admin SPA (config editor, source manager) |
/ogc/* |
tipg | None | OGC API (tiles, features, collections) |
Authentication is enforced at the Express API layer — the gateway passes all requests through. The admin-app's requireAuth middleware protects all mutating endpoints (POST, PUT, DELETE). All GET endpoints are public so the map client can fetch configurations without credentials.
To enable auth, set ADMIN_USERNAME and ADMIN_PASSWORD_HASH (bcrypt) environment variables on the admin-app service. When unset, auth is disabled (development mode).
Config names admin, api, and ogc are reserved to prevent conflicts with gateway routes.
- Declarative layer definitions with style specifications
- Multiple basemap support
- Initial view state (center, zoom, pitch, bearing)
- UI component visibility toggles
- Vector Tiles: Efficient streaming from tipg MVT endpoints
- GeoJSON: Client-side rendering via OGC API Features
- Dynamic Filtering: Queryable attributes per collection
- Pagination: Cursor-based navigation for large datasets
- LayerPanel: Toggle visibility, reorder layers, adjust opacity
- Legend: Auto-generated or custom legend entries
- BasemapSwitcher: Thumbnail-based basemap selection
- SearchPanel: Full-text and attribute search across collections
- FeatureDetailPanel: Click a feature to view its full properties
- FeatureTooltip: Hover preview of feature properties
- ExportButton: CSV export of visible layer data
- URL Synchronization: Shareable links with view state and filters
- Browser History: Back/forward navigation through map states
- Debounced Updates: Smooth viewport changes without URL spam
- Configuration: JSON config defines sources, layers, basemaps, UI
- Validation: Zod schemas validate config at runtime
- Hydration: Config populates Zustand stores in client app
- Data Fetching: Hooks fetch from tipg endpoints
- Rendering: MapLibre renders vector tiles; React components render UI
- URL Sync: nuqs keeps URL in sync with Zustand state
- User Interaction: Changes flow back through stores to URL
- Create component in
packages/map-ui-lib/src/components/ - Use
mapui:prefixed Tailwind classes - Create Storybook story (
.stories.tsx) - Export from component index and main entry point
- Add Vite entry point for tree-shaking
- Build and verify in Storybook
- Modify Zod schema in
schemas/config.ts - TypeScript types auto-infer from schema
- Update example config in
apps/map-client/src/config/ - Validation errors show at runtime
AGPL-3.0 — see LICENSE for details.
- Natural Earth: Free vector and raster map data
- tipg: OGC API implementation by Development Seed
- MapLibre GL JS: Open-source map rendering library