| name | fetch-backend-data |
| description | Guide for understanding the UWFlow data layer — inspecting the Postgres schema via psql, understanding how Hasura exposes it as GraphQL, and wiring up Apollo Client queries in the frontend. |
Fetch Backend Data
This skill guides you through the full data-fetching stack: Postgres → Hasura (GraphQL) → Apollo Client (React).
1. Understand the Schema via psql
Credentials live in uwflow/.env (copy of .env.sample):
POSTGRES_DB=flow
POSTGRES_HOST=postgres # or localhost if connecting outside Docker
POSTGRES_PASSWORD=secretinprod
POSTGRES_PORT=5432
POSTGRES_USER=postgres
Connect locally (backend must be running via docker-compose):
psql -h localhost -p 5432 -U postgres -d flow
Useful psql commands:
\dt
\d course
\d+ user_schedule
The canonical source of truth for the schema is the Hasura migrations:
- Init migration:
uwflow/hasura/migrations/default/1559740220527_init/up.sql — defines all base tables
- Subsequent migrations:
uwflow/hasura/migrations/default/*/up.sql — incremental alterations
Read these files to understand table structure before writing queries.
2. How Hasura Exposes the Schema as GraphQL
Hasura introspects the Postgres schema and automatically generates a GraphQL API — no resolver code needed. Every table becomes a queryable root field. Relationships between tables (foreign keys) become nested fields.
The Hasura console runs at http://localhost:8080.
Key mapping rules:
- Table
course → GraphQL root field course
- Filter with
where: { code: { _eq: $code } }
- Order with
order_by: { field: asc }
- Joins follow FK relationships:
course { course_sections { ... } }
3. Adding a Query to the Frontend
Step 1 — Write the query
Queries live in src/graphql/queries/. Create or edit a file there:
import { gql } from '@apollo/client';
export const GET_MY_DATA = gql`
query getMyData($id: Int!) {
my_table(where: { id: { _eq: $id } }) {
id
name
related_table {
field
}
}
}
`;
Reuse shared fragments from src/graphql/fragments/ where possible.
Step 2 — Use the query in a component
import { useQuery } from '@apollo/client';
import { GET_MY_DATA } from 'graphql/queries/my_feature/MyFeature';
const MyComponent = ({ id }: { id: number }) => {
const { data, loading, error } = useQuery(GET_MY_DATA, {
variables: { id },
});
if (loading) return <Spinner />;
if (error || !data) return null;
return <div>{data.my_table[0].name}</div>;
};
Step 3 — Regenerate TypeScript types (if needed)
Generated types live in src/generated/graphql.tsx. If the schema or queries changed, regenerate them per the project's codegen setup.
4. Authentication Context
Apollo is configured in src/graphql/apollo.js. It automatically attaches a JWT from localStorage as Authorization: Bearer <token>. Hasura uses HASURA_GRAPHQL_JWT_KEY to verify the token and applies row-level permissions based on the user role (anonymous vs authenticated).
Unauthenticated queries work as the anonymous role — only publicly visible data is returned. If a query returns empty results unexpectedly, check that the Hasura permissions for anonymous include the relevant table.
5. Quick Reference
| Layer | Location |
|---|
| Schema def | uwflow/hasura/migrations/default/*/up.sql |
| psql creds | uwflow/.env |
| Hasura UI | http://localhost:8080 |
| GQL queries | uwflow_frontend/src/graphql/queries/ |
| Fragments | uwflow_frontend/src/graphql/fragments/ |
| Apollo init | uwflow_frontend/src/graphql/apollo.js |
| Generated types | uwflow_frontend/src/generated/graphql.tsx |