Hasura GraphQL Engine: Auto-Generate GraphQL API from PostgreSQL Without Writing Backend Code

Database tutorial - IT technology blog
Database tutorial - IT technology blog

The Problem That Pushed Me Toward Hasura

Six months ago I was juggling three separate projects at once — one on MySQL, one on PostgreSQL, one on MongoDB. Each database has its genuine strengths. But every time the schema changed, I had to update resolver functions, REST controllers, and auth middleware in lockstep. One afternoon I spent four hours tracking down why a single renamed column broke three API endpoints and a mobile app. That debugging session finally convinced me something had to change.

The question wasn’t “which database is best” — it was “why am I hand-writing CRUD wrappers for the hundredth time?”

Root Cause: The API Layer Tax

Traditional backend development carries what I call the API layer tax. You have a perfectly good relational schema in PostgreSQL, but to expose it you need to:

  • Write route handlers or resolvers for every table and relationship
  • Implement filtering, pagination, and sorting logic separately
  • Add authentication middleware and wire it to row-level checks
  • Build WebSocket infrastructure if you need realtime updates
  • Keep all of this synchronized with every schema migration

For a 20-table schema, that easily balloons into thousands of lines of boilerplate before you’ve written a single line of actual business logic. Most of it is purely mechanical work — it follows the schema deterministically. Yet humans write it by hand, and that’s where the bugs creep in.

Solutions I Evaluated

Option 1: Manual REST API (FastAPI / Express)

Full control, familiar tooling, easy to customize. But the maintenance cost is real. Every ALTER TABLE becomes a multi-file change. I kept this for services with complex business rules, but not for data-heavy CRUD.

Option 2: PostgREST

PostgREST auto-generates a REST API directly from a PostgreSQL schema. Lightweight and fast. But REST means the client has no say over field selection — you can’t ask for exactly the fields you need, and there’s no built-in realtime. For read-heavy dashboards with GraphQL clients, it didn’t fit.

Option 3: Prisma + Apollo GraphQL Server

You get type-safe queries and a proper GraphQL layer with this combination. But you still write resolvers, manage the Prisma client, and configure subscriptions separately. An improvement over raw REST — not an elimination of the boilerplate.

Option 4: Hasura GraphQL Engine

Hasura connects to your existing PostgreSQL database and instantly generates a complete GraphQL API — queries, mutations, subscriptions — with no code. Permissions are configured through a UI or metadata YAML, not scattered across middleware. After six months in production, it’s the one I’d recommend for teams building data-driven apps without a large backend team.

Setting Up Hasura with Docker

The fastest way to run Hasura locally is with Docker Compose. Create a docker-compose.yml:

version: '3.6'
services:
  postgres:
    image: postgres:16
    restart: always
    environment:
      POSTGRES_PASSWORD: mysecretpassword
    volumes:
      - db_data:/var/lib/postgresql/data

  graphql-engine:
    image: hasura/graphql-engine:v2.40.0
    ports:
      - "8080:8080"
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:mysecretpassword@postgres:5432/postgres
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
      HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
      HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256","key":"your-256-bit-secret-here"}'
    depends_on:
      - postgres

volumes:
  db_data:

Start it:

docker compose up -d

Open http://localhost:8080/console and enter your admin secret. Setup done. Hasura is now pointing at your PostgreSQL instance.

Tracking Tables and Relationships

Hasura doesn’t expose tables automatically — you “track” them through the console or API. This is intentional: you might have internal tables (audit logs, migration history) you don’t want in your public API.

Say you have these tables:

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email TEXT UNIQUE NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  title TEXT NOT NULL,
  body TEXT,
  published BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

In the Hasura console, go to Data → public → Track All. Hasura detects foreign keys and suggests relationships. Accept the user → posts array relationship and the post → user object relationship. Now you can query nested data in a single request:

query GetUserPosts {
  users {
    email
    posts(where: { published: { _eq: true } }) {
      title
      created_at
    }
  }
}

No resolver code. The relationship traversal, join, and filtering all happen automatically.

Row-Level Security Without Custom Middleware

The permissions model caught me off guard — in a good way. In traditional setups, you either use PostgreSQL RLS policies directly (complex to manage at scale) or write authorization checks in every resolver (error-prone and easy to miss). Hasura takes a third path: define permission rules per role in the console, and it translates them into WHERE clauses before touching the database.

Example: users should only read their own posts.

In the console, go to Data → posts → Permissions → user role → Select, then set the row permission:

{
  "user_id": { "_eq": "X-Hasura-User-Id" }
}

X-Hasura-User-Id is a claim extracted from the JWT token Hasura receives on every request. When a logged-in user queries posts, Hasura automatically appends WHERE user_id = '<their-id>'. They cannot see anyone else’s posts — no matter what they put in the query.

Column-level visibility, row count limits, and separate rules for insert, update, and delete are all configurable per role from the same interface.

Realtime Subscriptions Without the WebSocket Plumbing

Implementing WebSocket-based realtime in a custom backend takes a weekend minimum. Connection handling, heartbeats, reconnect logic — it adds up fast. In Hasura, you change query to subscription:

subscription WatchNewPosts {
  posts(
    where: { published: { _eq: true } },
    order_by: { created_at: desc },
    limit: 10
  ) {
    id
    title
    created_at
  }
}

The client receives an update whenever the result set changes. Hasura polls PostgreSQL on a configurable interval (default 1 second) and pushes diffs to connected clients over WebSocket. For a live notification feed or dashboard, you get working realtime with no extra infrastructure to manage.

Managing Hasura in Production

Six months of production use taught me three practices that matter more than they look:

Use Hasura Metadata for Version Control

All permissions, relationships, and tracked tables are stored as metadata YAML files. Export them and commit to git:

# Install Hasura CLI
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash

# Initialize project
hasura init my-project --endpoint http://localhost:8080 --admin-secret myadminsecretkey
cd my-project

# Export current metadata
hasura metadata export

# Apply metadata to another environment
hasura metadata apply --endpoint https://staging.example.com

Promoting environments — local to staging to production — becomes a single command.

Never Expose the Admin Secret to Clients

The admin secret bypasses all permission rules. Set HASURA_GRAPHQL_ADMIN_SECRET only in server environments and use JWT tokens for all client-facing requests. Your auth service (Auth0, Supabase Auth, or a custom JWT issuer) issues tokens with Hasura-specific claims:

{
  "sub": "user-uuid-here",
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": ["user"],
    "x-hasura-default-role": "user",
    "x-hasura-user-id": "user-uuid-here"
  }
}

Use Actions for Business Logic

Hasura doesn’t replace backend code entirely — it replaces the data layer. For operations with side effects (sending emails, charging payments, calling third-party APIs), use Hasura Actions: define a custom GraphQL mutation that Hasura forwards to an HTTP handler you write. Data mutations stay in Hasura’s type-safe layer; side effects live in your handler.

Where Hasura Fits — and Where It Doesn’t

After six months, here’s my honest take: Hasura saves enormous time for data-heavy applications where the schema is the product. Internal tools, dashboards, admin panels, and mobile backends with standard CRUD patterns are its sweet spot.

Heavy transformation logic between the database and the client is a different story. Teams deeply invested in REST — and not ready to shift frontend querying to GraphQL — will find the migration cost outweighs the gains.

But if your team spends weekends writing “just another list endpoint with filters and pagination,” give Hasura a proper day. My PostgreSQL tables were tracked, permissioned, and serving a React frontend in under two hours — time I spent on actual product features instead.

Share: