Skip to main content

Tutorial: Launch a GraphQL API

The graphql-api stack stands up a production GraphQL API over your own PostgreSQL database, powered by Hasura. You launch one stack, and a few minutes later you have a live GraphQL endpoint with queries, mutations, real-time subscriptions, fine-grained permissions, and event triggers. There is no backend code to write and no resolver to maintain.

This tutorial walks through launching the stack from the console and from the API, creating your first table, watching Hasura expose it as GraphQL instantly, and running a query, a mutation, and a subscription against it.

Who this is for

This stack is for anyone who wants a GraphQL API over relational data without building and operating a backend:

  • Frontend and mobile teams that want a typed, real-time data layer over PostgreSQL.
  • Teams replacing hand-written REST endpoints with a single introspectable GraphQL schema.
  • Prototypes and internal tools that need an API on day one, not after a sprint of plumbing.

You should be comfortable with SQL and basic GraphQL. You do not need to know Hasura beforehand.

What gets composed

The stack is pure composition over two FoundryDB primitives, provisioned in dependency order and wired together for you.

ResourceWhat it isWhy it is here
dbPostgreSQL 17, tier-1 (1 vCPU, 2 GB), 25 GB standard storageThe system of record. Your tables, your data, owned by your account.
appA Hasura application, tier-1, 10 GB standard storage, attached to dbIntrospects the database schema and serves the GraphQL API and console over it.

Hasura does not own your data. PostgreSQL does. Hasura reads the database schema and projects a GraphQL API onto it, so the database remains an ordinary FoundryDB managed service you can inspect, back up, scale, and connect to directly.

graphql-api stack · compose & introspect
RUNNING Stack wired · GraphQL endpoint live
Stack Templategraphql-apilaunch ⇉PostgreSQLtables · :5432Hasurawired · introspectsintrospect →GraphQL APIqueries · mutations · subscriptions
TemplatePostgreSQLHasuraGraphQL API (introspected)wiring (env injected)

How it works

When the stack reaches the Wiring state, the engine attaches the Hasura app to the PostgreSQL service and injects HASURA_GRAPHQL_DATABASE_URL, the connection string Hasura uses to reach your database. Hasura connects, introspects the schema, and exposes every tracked table as GraphQL.

PostgreSQL schema  ──introspect──▶  Hasura  ──serves──▶  GraphQL API
(tables, FKs) (queries, mutations,
subscriptions, permissions)

Introspection is live. When you create or alter a table and track it, the GraphQL schema updates without a redeploy. Foreign keys become nested relationships in the graph, so a posts table with an author_id referencing users gives you posts { author { name } } for free.

Prerequisites

  • An API token. See Authentication.
  • Set it in your shell so the examples below work as written:
export FOUNDRYDB_TOKEN="your-api-token"

Unlike the rag-chatbot stack, graphql-api does not require a configured inference provider. It composes only a database and an app.

Launch it from the console

  1. Open the Stacks catalog in the console.
  2. Pick Launch a GraphQL API.
  3. Review the cost preview and accept it.
  4. Click Launch.

The stack provisions asynchronously. When it reaches Running, the detail view shows the live endpoint and links to the Hasura console.

Launch it from the API

The launch flow is always preview, then accept, then launch, then poll. You never create a billable resource without first accepting an up-to-date cost.

1. Preview the cost

curl -X POST https://api.foundrydb.com/stacks/preview \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"template_name": "graphql-api"}'

The response breaks the monthly cost down by resource:

{
"template_name": "graphql-api",
"currency": "EUR",
"monthly_total": 24.00,
"line_items": [
{
"symbolic_name": "db",
"kind": "database",
"description": "PostgreSQL tier-1 (1 vCPU, 2 GB) + 25 GB standard storage",
"monthly_cost": 14.00,
"is_ceiling": false
},
{
"symbolic_name": "app",
"kind": "app",
"description": "Hasura tier-1 (1 vCPU, 2 GB) + 10 GB standard storage",
"monthly_cost": 10.00,
"is_ceiling": false
}
]
}

The monthly_total is the figure you pass to the launch request. The numbers above are illustrative; always use the value returned by your own preview call, since pricing is computed fresh on every request.

2. Launch

Pass the monthly_total from the preview as accepted_monthly_cost:

curl -X POST https://api.foundrydb.com/stacks \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-graphql-api",
"template_name": "graphql-api",
"accepted_monthly_cost": 24.00
}'

The response is 201 Created with the stack in Pending status. Capture the stack ID for polling:

export STACK_ID="the-id-from-the-response"

If accepted_monthly_cost is omitted, or if it has drifted from the current estimate by more than EUR 0.01, the launch is rejected and you re-preview. See Launch a Stack for the full cost gate behaviour.

3. Poll until Running

The stack provisions asynchronously. Poll GET /stacks/{stack-id} until status reaches Running:

curl https://api.foundrydb.com/stacks/$STACK_ID \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN"

Typical progression is PendingProvisioningWiringRunning. Most stacks complete within 5 minutes. Once the stack is Running, the endpoint_url field carries the live Hasura URL:

{
"id": "...",
"name": "my-graphql-api",
"status": "Running",
"endpoint_url": "https://my-graphql-api.foundrydb.com",
"resources": [
{ "symbolic_name": "db", "kind": "database", "status": "Running", "service_id": "..." },
{ "symbolic_name": "app", "kind": "app", "status": "Running", "service_id": "..." }
]
}

The endpoint_url serves both the Hasura console and the GraphQL endpoint. The GraphQL endpoint is at /v1/graphql on that host.

First run: from a table to a GraphQL API

Now create a table and watch Hasura expose it instantly.

1. Open the Hasura console

Open the endpoint_url from the previous step in your browser. The Hasura console gives you a schema explorer, a data tab, a permissions editor, and an interactive GraphQL playground (GraphiQL).

2. Create a table

In the console, go to the Data tab and create a table. You can use the UI or run SQL directly from the SQL runner:

CREATE TABLE authors (
id serial PRIMARY KEY,
name text NOT NULL,
email text UNIQUE NOT NULL
);

CREATE TABLE posts (
id serial PRIMARY KEY,
title text NOT NULL,
body text,
author_id integer NOT NULL REFERENCES authors(id),
created_at timestamptz NOT NULL DEFAULT now()
);

Track both tables when prompted (the SQL runner has a Track this checkbox; the Data tab lists untracked tables with a Track button). Tracking is what tells Hasura to project a table into the GraphQL schema.

3. Watch it become GraphQL

As soon as the tables are tracked, Hasura exposes them. Open the API tab (GraphiQL) and run a query. The foreign key from posts to authors is automatically modeled as a nested relationship:

query GetPostsWithAuthors {
posts {
id
title
created_at
author {
name
email
}
}
}

You wrote no resolver. The nested author field exists because the foreign key exists.

4. Run a mutation

Insert a row through the API:

mutation AddAuthor {
insert_authors_one(object: { name: "Ada Lovelace", email: "ada@example.eu" }) {
id
name
}
}

Expected response:

{
"data": {
"insert_authors_one": {
"id": 1,
"name": "Ada Lovelace"
}
}
}

5. Subscribe in real time

Subscriptions are live queries over a WebSocket. Run this in one GraphiQL tab, then insert a post in another, and watch the result update on its own:

subscription LivePosts {
posts(order_by: { created_at: desc }, limit: 10) {
id
title
author { name }
}
}

This is the same query syntax as a normal read, but the client receives a new result set whenever the underlying rows change. No polling.

6. Set a permission

GraphQL is exposed publicly by default for tracked tables when no role is restricting access, so before you ship anything, define a role. In the Permissions tab for the posts table, add a user role and a select permission with a row filter, for example only rows the requesting user authored:

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

Hasura reads the X-Hasura-User-Id session variable from each request and applies the filter at the database layer, so a user only ever sees their own rows. You can scope columns, rows, inserts, updates, and deletes per role, all without writing authorization code in an application server.

Cost and EU residency

Both resources in this stack are billed as ordinary FoundryDB services on your account. The cost preview itemizes them before launch, and the accepted figure is re-checked at launch time. There is no surprise bill.

Like every stack, graphql-api is composed entirely from EU-resident primitives. The PostgreSQL database and the Hasura app both run within the platform's European footprint. Your data and your GraphQL traffic stay in Europe.

Teardown

Deleting the stack removes both child resources atomically, in reverse dependency order:

curl -X DELETE https://api.foundrydb.com/stacks/$STACK_ID \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN"

Returns 202 Accepted. No child resource is left orphaned. Teardown is irreversible, so export anything you want to keep first.

What's next

  • The PostgreSQL service is yours. Connect to it directly, add extensions, scale it, and back it up like any managed database. See the Stacks Overview on ownership.
  • Read Launch a Stack for the full lifecycle, including retry, overrides, and the cost gate.
  • Want a different shape over the same database? Browse the catalog in the Stacks Overview for the CMS, BI workspace, and no-code database stacks.