Case study
Unified Go backend and Cognito sign-in
Merged four product backends into one Go service and moved every application to AWS Cognito sign-in, with a scoped partner API at the edge.
- Result
- 100K+ daily requests, P95 latency down 40%, and a production API adopted by FFC, Corteva, Engro and Kubota.
- Stack
- GoAWS CognitoOAuth 2.0PostGISAPI GatewayKubernetes
Context
Farmdar grew four products (YieldPro, CropScan, eSurvey and DeveloPro), each with its own backend, database and login. Every cross-product feature meant changes in several places, and users needed separate accounts.
How it works
One Go binary serves every product as a module under a single API, backed by one PostgreSQL/PostGIS database with an optional read-only pool for heavy queries. It runs on EKS in hardened pods: non-root, read-only file system and images pinned by digest.
Sign-in uses Cognito's hosted UI with the authorization-code flow. Mobile apps use PKCE, and staff sign in with Google Workspace through SAML. The Go service verifies tokens locally against cached signing keys (JWKS), with no network call per request. Behind the load balancer it verifies the load balancer's own signed token.
Partners use a separate entry point: API Gateway with a Cognito authorizer, machine-to-machine clients and read and write scopes, connected privately to the service. Only a few routes are exposed there.
Key decisions
Unify the data first
The first release merged the four products' data into one schema and backend in a single large change, with migration scripts for each product. After that, new features were ordinary modules instead of cross-service projects.
Migrate auth behind a feature flag
The Cognito rollout ran in three phases. For a while the backend accepted both old and Cognito tokens, using a cheap header check to tell them apart, and Cognito was switched on per environment. Deploying the code changed nothing until the flag was turned on.
The database decides who can see what
Tokens prove identity. Organisation and group membership come from the database, cached for a few minutes, so access changes take effect without reissuing tokens.
Create the identity first, then the user
New users are created in Cognito first and then in the database in one transaction. If the database step fails, the Cognito user is deleted, so the database never points to an identity that doesn't exist.
Timeouts per handler, not globally
Exports and large GeoJSON responses can legitimately take longer than the gateway's 29-second limit. Instead of one global timeout, handlers validate early and set their own limits.
My role
I led the unification release and ran the Cognito migration from design to cutover, including multi-organisation switching, atomic user provisioning, the partner API and replacing static CI deploy keys with GitHub OIDC.
Next case study
Crop intelligence: from models to products →