Skip to main content
Technology & EngineeringBaas316 lines

Hasura

Hasura is an open-source engine that connects to your databases (PostgreSQL, MS SQL, etc.) and instantly gives you a production-ready GraphQL API. It simplifies data access, real-time subscriptions, and authorization management, making it ideal for rapidly developing data-driven applications.

Quick Summary19 lines
You are an expert in Hasura, proficient in leveraging its real-time GraphQL APIs, robust authorization system, and eventing capabilities to build scalable, data-driven web applications. You know how to connect it to various databases, manage schema migrations, and integrate it seamlessly with frontend frameworks using GraphQL clients.

## Key Points

*   **Batch Mutations:** For operations involving multiple inserts or updates, use `insert_many` or `update_many` mutations where applicable to reduce network overhead and improve efficiency.

## Quick Example

```bash
# Create a docker-compose.yaml file
touch docker-compose.yaml
```

```bash
# Start Hasura and PostgreSQL
docker-compose up -d
```
skilldb get baas-skills/HasuraFull skill: 316 lines
Paste into your CLAUDE.md or agent config

You are an expert in Hasura, proficient in leveraging its real-time GraphQL APIs, robust authorization system, and eventing capabilities to build scalable, data-driven web applications. You know how to connect it to various databases, manage schema migrations, and integrate it seamlessly with frontend frameworks using GraphQL clients.

Core Philosophy

Hasura's core philosophy is to provide an instant, high-performance GraphQL API over your existing databases, eliminating much of the boilerplate associated with building a traditional backend. It focuses on declarative configuration for data access, real-time capabilities, and security, allowing developers to focus on application logic rather than API plumbing. Hasura takes your database schema and automatically generates a comprehensive GraphQL API, complete with querying, mutating, and real-time subscription capabilities.

You choose Hasura when you need to rapidly expose your database data via a GraphQL API, especially when real-time updates are critical or when you're orchestrating data from multiple services. It excels in scenarios where you have an existing database, want to build highly interactive applications, or need a powerful authorization layer without writing extensive custom backend code. It acts as a powerful data access layer, allowing you to centralize data interactions and enforce security policies close to the data source.

Setup

Setting up Hasura typically involves deploying the Hasura Engine and connecting it to your database. For client-side integration, you'll use a GraphQL client like Apollo Client or Relay.

Deploying Hasura Engine (Docker)

For local development or self-hosting, Docker is the most common way to run Hasura.

# Create a docker-compose.yaml file
touch docker-compose.yaml
# docker-compose.yaml
version: '3.6'
services:
  postgres:
    image: postgres:15
    restart: always
    volumes:
      - db_data:/var/lib/postgresql/data
    env_file:
      - .env
  hasura:
    image: hasura/graphql-engine:v2.36.0 # Use a specific stable version
    ports:
      - "8080:8080"
    restart: always
    depends_on:
      - postgres
    env_file:
      - .env
volumes:
  db_data:
# .env file for database and Hasura configuration
# .env
POSTGRES_DB=mydatabase
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
HASURA_GRAPHQL_DATABASE_URL=postgres://myuser:mypassword@postgres:5432/mydatabase
HASURA_GRAPHQL_ENABLE_CONSOLE=true
HASURA_GRAPHQL_ADMIN_SECRET=myadminsecretkey # IMPORTANT: Change in production
# Start Hasura and PostgreSQL
docker-compose up -d

Access the Hasura Console at http://localhost:8080.

Client-Side Setup (React with Apollo Client)

npm install @apollo/client graphql
// src/apollo-client.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = new HttpLink({
  uri: 'http://localhost:8080/v1/graphql', // Your Hasura GraphQL endpoint
});

const authLink = setContext((_, { headers }) => {
  // Get the authentication token from local storage if it exists
  const token = localStorage.getItem('jwtToken'); // Or from an auth context
  // Return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});
// src/main.tsx or src/App.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ApolloProvider } from '@apollo/client';
import { client } from './apollo-client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
);

Key Techniques

1. Querying Data

You query data using standard GraphQL syntax, leveraging Hasura's auto-generated fields and relationships.

// src/App.tsx
import { gql, useQuery } from '@apollo/client';

const GET_TODOS = gql`
  query GetTodos {
    todos(order_by: {id: asc}) {
      id
      title
      is_completed
    }
  }
`;

interface Todo {
  id: number;
  title: string;
  is_completed: boolean;
}

interface GetTodosData {
  todos: Todo[];
}

function TodosList() {
  const { loading, error, data } = useQuery<GetTodosData>(GET_TODOS);

  if (loading) return <p>Loading todos...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>My Todos</h2>
      <ul>
        {data?.todos.map(todo => (
          <li key={todo.id}>
            {todo.title} - {todo.is_completed ? 'Done' : 'Pending'}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodosList;

2. Mutating Data (Create, Update, Delete)

Hasura provides mutations for inserting, updating, and deleting data, following GraphQL mutation patterns.

// src/components/AddTodoForm.tsx
import React, { useState } from 'react';
import { gql, useMutation } from '@apollo/client';

const ADD_TODO = gql`
  mutation AddTodo($title: String!) {
    insert_todos_one(object: {title: $title}) {
      id
      title
      is_completed
    }
  }
`;

function AddTodoForm() {
  const [title, setTitle] = useState('');
  const [addTodo, { loading, error }] = useMutation(ADD_TODO, {
    refetchQueries: ['GetTodos'], // Refetch the GetTodos query after adding
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!title.trim()) return;
    addTodo({ variables: { title } });
    setTitle('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="New todo title"
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Adding...' : 'Add Todo'}
      </button>
      {error && <p>Error adding todo: {error.message}</p>}
    </form>
  );
}

export default AddTodoForm;

3. Real-time Subscriptions

You use subscriptions to get real-time updates for data changes. Hasura leverages WebSockets for this.

// src/components/LiveTodos.tsx
import { gql, useSubscription } from '@apollo/client';

const LIVE_TODOS = gql`
  subscription LiveTodos {
    todos(where: {is_completed: {_eq: false}}, order_by: {id: asc}) {
      id
      title
      is_completed
    }
  }
`;

interface Todo {
  id: number;
  title: string;
  is_completed: boolean;
}

interface LiveTodosData {
  todos: Todo[];
}

function LiveTodos() {
  const { loading, error, data } = useSubscription<LiveTodosData>(LIVE_TODOS);

  if (loading) return <p>Loading live todos...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h3>Live Pending Todos</h3>
      <ul>
        {data?.todos.map(todo => (
          <li key={todo.id}>
            {todo.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default LiveTodos;

4. Authorization with JWT

Hasura integrates with JWTs to provide powerful row-level and column-level authorization. You send a JWT in the Authorization header, and Hasura uses its claims (e.g., x-hasura-user-id, x-hasura-role) to enforce access rules.

// Example of how a JWT payload might look (after being signed by your auth service)
{
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": ["user", "admin"],
    "x-hasura-default-role": "user",
    "x-hasura-user-id": "123",
    "x-hasura-org-id": "456"
  },
  "sub": "auth0|12345",
  "iat": 1516239022,
  "exp": 1516242622
}

When making a request with Apollo Client, the authLink from the setup section automatically attaches this token. Hasura then interprets the x-hasura-* claims to apply the configured permissions. For instance, you can define a rule like "a user can only see todos where user_id matches x-hasura-user-id".

Best Practices

  • Use Migrations for Schema Changes: Always manage your database schema and Hasura metadata changes using the Hasura CLI's migration system. This ensures version control, consistency across environments, and proper rollback capabilities.
  • Implement Robust Authorization: Leverage Hasura's powerful Role-Based Access Control (RBAC) and Row-Level Security (RLS) extensively. Define granular permissions for every role and table to prevent unauthorized data access.
  • Optimize Queries with Indices: While Hasura provides the API, database performance is still key. Ensure your database tables have appropriate indices for fields frequently used in where clauses, order_by, and join conditions.
  • Batch Mutations: For operations involving multiple inserts or updates, use insert_many or update_many mutations where applicable to reduce network overhead and improve efficiency.
  • Monitor Performance: Utilize Hasura's built-in metrics and integrate with external monitoring tools to track API performance, slow queries, and error rates. Optimize your database or Hasura configuration based on these insights.
  • Secure Environment Variables: Never hardcode sensitive information like HASURA_GRAPHQL_ADMIN_SECRET or database credentials. Use environment variables, and ensure they are managed securely in production.
  • Generate Types for Client-Side: Use GraphQL code generation tools (e.g., GraphQL Code Generator) to create TypeScript types from your Hasura schema and queries. This provides type safety and a better developer experience.

Anti-Patterns

Ignoring Authorization. Failing to define comprehensive Row-Level Security and permissions means your data is exposed. Always assume malicious intent and define strict access rules for every role, table, and column.

Manual Schema Management. Directly modifying your database schema or Hasura metadata via the console in production environments leads to inconsistencies and difficult debugging. Always use the Hasura CLI for migrations and metadata application.

Over-fetching or Under-fetching Data. Sending overly broad queries (over-fetching) or making many small queries instead of one optimized query (under-fetching) leads to inefficient network usage. Leverage GraphQL's flexibility to fetch exactly what you need in a single request.

Client-Side Data Joins. Attempting to join data from multiple tables by fetching them separately on the client. Instead, define relationships in Hasura and use nested GraphQL queries to fetch related data efficiently in a single trip.

Using Admin Secret in Client-Side Code. Exposing your HASURA_GRAPHQL_ADMIN_SECRET in any client-side application is a critical security vulnerability, granting full unrestricted access to your Hasura instance. The admin secret should only be used by trusted backend services or for initial setup, never in the browser.

Install this skill directly: skilldb add baas-skills

Get CLI access →