--- path: 2026_03_30_GISOwl_Architecture_Go_Postgres title: "Building GISOwl on Go, PostgreSQL, and React: Architecture Decisions" desc: "Why GISOwl chose Go, PostgreSQL with PostGIS and pgvector, and React for its mapping platform architecture." date: Mar 30, 2026 --- # Building GISOwl on Go, PostgreSQL, and React: Architecture Decisions *March 2026* Most web-based mapping platforms are built on Python with GeoDjango, or Node.js with Mapbox GL wrappers. GISOwl isn't. Our backend is Go with the Echo framework, our database is PostgreSQL with PostGIS and pgvector extensions, and our frontend is React. Here's why we made these choices and what we learned. ## Why Go Geospatial backends are deceptively compute-heavy. Every map view triggers tile rendering, permission checks, and layer composition. Every spatial query hits geometric intersections across millions of features. Go's concurrency model — goroutines and channels — handles this naturally. Our API response times sit at p50 under 15ms and p99 under 80ms for authenticated endpoints. We didn't have to think about async frameworks or event loops; Go's scheduler handles the concurrency we need out of the box. The other advantage is deployment simplicity. A Go binary is a single static file. We deploy on Dokku, and our entire backend is one container with no runtime dependencies beyond PostgreSQL. ## Why PostgreSQL (with PostGIS and pgvector) Every mapping platform eventually needs three things beyond basic CRUD: spatial queries, full-text search, and vector similarity for AI features. PostgreSQL handles all three natively with extensions. **PostGIS** is the backbone of GISOwl — it handles geometry storage, spatial indexing, intersection queries, buffer analysis, coordinate transformations, and raster operations. Features like "find all parcels within 500m of this river" or "calculate the area of these overlapping polygons" run as standard SQL. **pgvector** powers our Owl AI features — we store embeddings for map descriptions, layer metadata, and user queries, enabling semantic search ("find maps similar to this environmental survey") without a separate vector database. We use `sqlx` for database access with a codegen pattern: standard CRUD operations are generated from schema definitions, while business logic lives in custom controllers. Table names follow an `hh_` prefix convention for namespacing. ## Why React (Not a SPA Framework) Our frontend is React with server-side rendered marketing pages served from the Go backend. The mapping application itself is a SPA at `app.gisowl.com`, but marketing pages are pre-rendered HTML templates served directly by Echo. This split — SSR for marketing, SPA for app — gives us the best of both worlds: crawlable marketing pages for SEO and a fast, interactive mapping experience for logged-in users. The map editor uses deck.gl and Mapbox GL JS under the hood. ## Tile Serving and Layer Rendering One of the most critical architectural decisions was how to serve map tiles. We generate vector tiles on the fly from PostGIS using ST_AsMVT, cached at the edge. This means uploaded data is immediately visible on the map without a pre-processing pipeline. For raster imagery, we use a tile server that reads Cloud Optimized GeoTIFFs (COGs) directly from object storage, serving only the pixels needed for the current viewport and zoom level. ## What We'd Do Differently If starting over, we'd invest in OpenAPI spec generation from day one. Our API grew organically and the documentation lagged behind. We're now retroactively building a Swagger spec, but it would have been cheaper to generate it from annotated handlers from the start. We'd also set up `robots.txt` and `sitemap.xml` as static files served outside the Echo router from day one. Routing these through Echo's catch-all handler caused a subtle bug where 404s returned JSON instead of HTML — something that went unnoticed for months.