Go REST API behind Roda Belém, the crowdsourced accessibility map for Belém, Pará. Places, reviews with accessibility tagging, users with points, favourites — with the geospatial and filtering work pushed into SQL where it belongs.
- sqlc instead of an ORM. Queries live as real SQL in
sql/queries/{places,reviews,users,favorites}.sqland are compiled into a typed Go package (internal/infra/mysql/db). No query builder guessing, no runtime reflection — andsqlc.yamloverridesdecimal → float64so the generated types match the domain. - Radius search in the database. Nearby lookup is
ST_DISTANCE_SPHERE(POINT(lat, lng), POINT(?, ?)) <= ?(MySQL 8 spatial), so the API never pages a city's venues into memory to filter them. - Accessibility filtering pushed down too.
FIND_IN_SET(?, accessibility_features)answers "show me every place with a ramp" in one indexed pass. - A cuckoo filter in front of Google Places.
FindNearbyPlacesUseCasekeeps a 10M-entrypanmari/cuckoofilterof seen place IDs to drop duplicates before enrichment, which is what makes repeated map pans cheap. - Ports and adapters, honestly applied.
domain/declaresPlacesGateway,ReviewsGateway,UsersGateway;usecase/depends only on those interfaces;infra/repository/implements them against MySQL. Swapping the store touches one directory. - JWT at the middleware layer.
infra/web/middlewares/authentication_jwt.goguards the mutating routes;POST /users/loginreturns the token in theAuthorizationresponse header.
cmd/app/main.go → wiring: repositories → use cases → handlers → chi router
internal/
domain/ → entities + gateway interfaces
usecase/{places,review,user}/
infra/
web/ → handlers, webserver.go, JWT middleware, CORS
repository/ → MySQL implementations of the gateways
mysql/db/ → sqlc-generated queries (do not edit by hand)
sql/
migrations/ → schema (goose format)
queries/ → the SQL sqlc compiles
| Table | Columns of note |
|---|---|
users |
id UUID, email unique, avatar MEDIUMBLOB, points INT, missions JSON |
places |
google_place_id, lat/lng FLOAT, types/opening_periods/photos JSON, rating |
reviews |
place_id → places, user_id → users, accessibility_features TEXT, reactions JSON (cascading delete) |
favorites |
place_id + user_id many-to-many (cascading delete) |
Places — POST /places/create · GET /places/find?id= · GET /places/nearby?lat=&lng=&radius=&isFromGoogle= · GET /places/accessibility?feature= · POST /places/update · DELETE /places/delete?id=
Reviews — GET /reviews/find?id= · GET /reviews/find-by-userid?userId= · GET /reviews/find-by-placeid?placeId= · (auth) POST /reviews/create · POST /reviews/update · DELETE /reviews/delete?id= · GET /reviews/add-feature?reviewId=&feature=
Users — POST /users/create · POST /users/login · GET /users/list · GET /users/find?id= · GET /users/find-by-email?email= · (auth) POST /users/update · GET /users/update-points?userId=&points= · GET /users/add-favorite?userId=&placeId= · DELETE /users/delete-favorite?userId=&placeId= · GET /users/favorite?userId=
Go 1.20 · chi v5 · sqlc · MySQL 8 · JWT · googlemaps/maps for Place enrichment · viper · rs/cors · cuckoofilter · Docker + Compose
cp .env.example .env # DB_USER DB_PASSWORD DB_HOST DB_PORT DB_NAME
# GOOGLE_MAPS_API_KEY JWT_SECRET_KEY
docker compose up -d # API on :8080, MySQL on :3306
# or
go run cmd/app/main.goRegenerate the query layer after editing sql/queries/:
sqlc generateFeature-complete: full CRUD for places, reviews and users, geospatial and accessibility filtering, favourites, points, JWT auth. Known gaps: pagination exists only on review queries, request validation is thin, and config failures panic rather than degrade. The old hosted instance is offline — run it with Compose.