Skip to content

About

Backend less ready to deploy hackathon solution

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hasura-keycloak-backendless

Simple all in one redy to go backend: Hasura for API, Keycloak for Auth API, Postgres as Database.

Services

Service Purpose Default
postgres Database always on
hasura-api Production GraphQL API always on
hasura-admin Console, metadata, config, pgdump optional private service
hasura-cli Migrations and metadata tool profile tools
keycloak OpenID Connect provider always on

Ports

Port Service Access
8080 GraphQL API public
8082 Keycloak public
8081 Admin Hasura private
9695 CLI Console local only

Do not expose 8081 to the internet.

Setup

Create .env:

POSTGRES_PASSWORD=postgres
HASURA_GRAPHQL_ADMIN_SECRET=dev-secret
KEYCLOAK_ADMIN_PASSWORD=dev-keycloak

Optional env vars for dedicated Hasura metadata storage and Keycloak storage:

# Defaults to POSTGRES_DB (or app)
POSTGRES_METADATA_DB=hasura_metadata

# Defaults to POSTGRES_USER (or hasura)
POSTGRES_METADATA_USER=hasura_metadata

# Defaults to POSTGRES_PASSWORD
POSTGRES_METADATA_PASSWORD=metadata-password

# Defaults to POSTGRES_DB (or app)
KEYCLOAK_DB_DATABASE=keycloak

# Defaults to POSTGRES_USER (or hasura)
KEYCLOAK_DB_USER=keycloak

# Defaults to POSTGRES_PASSWORD
KEYCLOAK_DB_PASSWORD=keycloak-password

For production, use long random secrets.

Run: ./init.sh

The script is idempotent and does everything automatically:

  • reads values from .env
  • if a value is missing, tries to use defaults from docker-compose.yml
  • starts postgres, prepares optional metadata/keycloak DB users and databases, then starts keycloak, then hasura-api
  • checks readiness of Keycloak and Hasura
  • creates or updates Keycloak realm settings (including frontend URL)
  • creates or updates Keycloak client settings (including redirect/web origins)
  • creates/updates protocol mappers for Hasura claims:
    • x-hasura-default-role
    • x-hasura-allowed-roles
    • x-hasura-user-id
  • creates/updates a test user and assigns the default role
  • runs an integration smoke test:
    • gets a real user access token from Keycloak
    • executes GraphQL query in Hasura using Authorization: Bearer <token>

Optional env vars for init:

KEYCLOAK_CLIENT_ID=frontend-app
KEYCLOAK_FRONTEND_ORIGIN=http://localhost:3000
KEYCLOAK_REALM_FRONTEND_URL=http://127.0.0.1:8082
KEYCLOAK_TEST_USERNAME=init-user
KEYCLOAK_TEST_PASSWORD=init-pass
HASURA_DEFAULT_ROLE=user
HASURA_API_URL=http://127.0.0.1:8080

Keycloak and Hasura auth

Hasura validates Bearer access tokens from Keycloak via HASURA_GRAPHQL_JWT_SECRET. The compose file already configures:

  • jwk_url: http://keycloak:8080/realms/$KEYCLOAK_REALM/protocol/openid-connect/certs
  • issuer: $KEYCLOAK_PUBLIC_URL/realms/$KEYCLOAK_REALM
  • claims_namespace_path: $ (Hasura reads x-hasura-* claims from token root)

Realm/client/mappers are configured by ./init.sh from the Setup section. If frontend URLs or realm settings change, run ./init.sh again.

1) Optional manual check in Keycloak UI

Open:

http://127.0.0.1:8082/admin

Verify:

  • realm exists
  • client redirect URI and web origin match your frontend URL
  • mappers for x-hasura-* claims are present

2) Configure Hasura permissions

In Hasura Console:

  • Track tables
  • Create permissions for role user (and other roles if needed)
  • Use x-hasura-user-id in row-level checks

Example filter:

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

3) Frontend flow

Frontend authenticates in Keycloak, gets an access token, then calls Hasura:

import Keycloak from "keycloak-js";

const keycloak = new Keycloak({
  url: "http://127.0.0.1:8082",
  realm: "hasura",
  clientId: "frontend-app"
});

await keycloak.init({ onLoad: "login-required", pkceMethod: "S256" });

const token = keycloak.token;

await fetch("http://127.0.0.1:8080/v1/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`
  },
  body: JSON.stringify({
    query: "query { __typename }"
  })
});

Do not use x-hasura-admin-secret in frontend code.

Start production

Starts runtime services.

docker compose up -d

Check health:

curl http://127.0.0.1:8080/healthz

GraphQL endpoint:

http://127.0.0.1:8080/v1/graphql

Hasura project

Create the project folder:

mkdir -p hasura

Create hasura/config.yaml:

version: 3
endpoint: http://hasura-admin:8080
metadata_directory: metadata
migrations_directory: migrations
seeds_directory: seeds

The endpoint uses the Docker network name, not localhost.

Run Hasura CLI

Show CLI version:

docker compose --profile tools run --rm hasura-cli version

Export metadata:

docker compose --profile tools run --rm hasura-cli metadata export

Reload metadata:

docker compose --profile tools run --rm hasura-cli metadata reload

Create a migration manually

Create an empty migration:

docker compose --profile tools run --rm hasura-cli migrate create create_some_tables

Edit the generated files:

hasura/migrations/default/<timestamp>_create_some_tables/up.sql
hasura/migrations/default/<timestamp>_create_some_tables/down.sql

Example up.sql:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE public.users (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  email text NOT NULL UNIQUE,
  name text,
  created_at timestamptz NOT NULL DEFAULT now()
);

Example down.sql:

DROP TABLE IF EXISTS public.users;

Apply locally:

docker compose --profile tools run --rm hasura-cli migrate apply --database-name default

Track the table in Console:

Data → Databases → default → Track

Then export metadata: docker compose --profile tools run --rm hasura-cli metadata export

Commit:

git add hasura
git commit -m "Add some table"

Apply changes in production

Pull the new code.

Start admin mode only for deploy work:

docker compose up -d postgres hasura-admin

Apply migrations:

docker compose --profile tools run --rm hasura-cli migrate apply --database-name default

Apply metadata:

docker compose --profile tools run --rm hasura-cli metadata apply

Reload metadata:

docker compose --profile tools run --rm hasura-cli metadata reload

Restart the production API:

docker compose up -d hasura-api

Stop admin mode after deploy (optional):

docker compose stop hasura-admin

Backup

Create backup:

docker compose exec postgres pg_dump \
  -U "$POSTGRES_USER" \
  -d "$POSTGRES_DB" \
  -Fc \
  -f /tmp/app.dump

Copy backup to host:

docker compose cp postgres:/tmp/app.dump ./app.dump

Restore

Copy backup into container:

docker compose cp ./app.dump postgres:/tmp/app.dump

Restore:

docker compose exec postgres pg_restore \
  -U "$POSTGRES_USER" \
  -d "$POSTGRES_DB" \
  --clean \
  --if-exists \
  /tmp/app.dump

Production notes

Keep hasura-api as the only runtime endpoint.

Keep hasura-admin behind localhost, SSH, or VPN.

Do not expose metadata, config, or pgdump APIs to the public.

Use permissions for every role.

Use allowlist when the frontend queries are stable.

Disable query-log in production if requests may contain private data.

Recommended production value:

HASURA_GRAPHQL_ENABLED_LOG_TYPES=startup,http-log,webhook-log,websocket-log

Common commands

  • Start production: docker compose up -d
  • Start admin: docker compose up -d postgres hasura-admin
  • Open admin console: http://127.0.0.1:8081 (use HASURA_GRAPHQL_ADMIN_SECRET)
  • Apply migrations: docker compose --profile tools run --rm hasura-cli migrate apply --database-name default
  • Apply metadata: docker compose --profile tools run --rm hasura-cli metadata apply
  • Reload metadata: docker compose --profile tools run --rm hasura-cli metadata reload
  • Run auth e2e tests: cd test && yarn test
  • Stop everything: docker compose down
  • Stop and delete database volume: docker compose down -v

About

Backend less ready to deploy hackathon solution

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages