A Fastify-based REST API that translates the MINT Model Catalog OpenAPI spec into GraphQL queries against a Hasura backend.
The API connects to Hasura via two environment variables:
| Variable | Default | Description |
|---|---|---|
HASURA_GRAPHQL_URL | http://testing-mint-hasura.mint.svc.cluster.local/v1/graphql | Hasura GraphQL endpoint |
HASURA_ADMIN_SECRET | CHANGEME | Hasura admin secret (used for reads) |
PORT | 3000 | Server port |
LOG_LEVEL | info | Pino log level (trace, debug, info, warn, error) |
For local development, point to your Hasura instance:
export HASURA_GRAPHQL_URL=http://localhost:8080/v1/graphql
export HASURA_ADMIN_SECRET=myadminsecretkey- Reads use the admin secret (
X-Hasura-Admin-Secretheader). No JWT required. - Writes (POST/PUT/DELETE) forward the caller's
Authorization: Bearer <token>to Hasura. Hasura validates the JWT and enforces row-level permissions. The API itself does not validate tokens.
npm install
npm run dev # tsx watch — restarts on file changesnpm run build # Compile TypeScript to dist/
npm start # Run compiled output
npm test# Run all tests with vitest
npm run codegen # Regenerate GraphQL types from Hasura schemanpx vitest run src/__tests__/integration.test.tsIntegration tests make real HTTP requests against a live API instance to verify end-to-end behavior (e.g., junction-based relationship CRUD). They are skipped by default when running npm test so they don't break unit test runs or CI.
To run them:
Obtain a Tapis Bearer token from https://portals.tapis.io/v3/oauth2/webapp
Set environment variables:
export MINT_API_TOKEN="<your-tapis-bearer-token>"export MINT_API_URL="https://api.models.mint.local/v2.0.0"# optional, this is the defaultexport NODE_TLS_REJECT_UNAUTHORIZED=0 # only if using self-signed certs
Run the integration tests:
npm test -- junction-integration
| Variable | Default | Description |
|---|---|---|
MINT_API_TOKEN | (none) | Tapis Bearer token. Suite is skipped when unset. |
MINT_API_URL | https://api.models.mint.local/v2.0.0 | API base URL to test against |
NODE_TLS_REJECT_UNAUTHORIZED | 1 | Set to 0 to allow self-signed TLS certificates |
GET /health
Returns { status: "ok", hasura: "connected" } or 503 if Hasura is unreachable.
Swagger UI is served at /v2.0.0/docs when the server is running.
docker build -t model-catalog-api .
docker run -p 3000:3000 \
-e HASURA_GRAPHQL_URL=http://your-hasura/v1/graphql \
-e HASURA_ADMIN_SECRET=your-secret \
model-catalog-apiHTTP request
-> Fastify (fastify-openapi-glue maps operationId -> handler)
-> CatalogService Proxy (service.ts)
-> CatalogServiceImpl (list / getById / create / update / deleteResource)
-> Apollo Client (hasura/client.ts)
-> Hasura GraphQL
src/service.ts— A JavaScriptProxywrapsCatalogServiceImpland intercepts all operationId method calls fromfastify-openapi-glue. It parses the operationId pattern ({resource}_(id_)?(get|post|put|delete)) and dispatches to the correct generic CRUD handler. This replaces ~230 individual handler files with a single class.src/hasura/client.ts— Two Apollo Client instances:readClient(uses admin secret, shared singleton) andgetWriteClient(bearerToken)(per-request, forwards user JWT for Hasura permission enforcement).src/hasura/field-maps.ts— Field selections per Hasura table, used to build dynamic GraphQL queries.src/mappers/resource-registry.ts— Maps the 46 API resource path segments to their Hasura table name, OWL type URI, and relationship configuration (direct, object, array, junction-table).src/mappers/response.ts/src/mappers/request.ts— Transform between Hasura row format and the v1.8.0-compatible JSON the API returns.src/custom-handlers.ts— Handlers for the 13/custom/endpoints and/user/login. These perform multi-table aggregation queries that cannot be served by the generic Proxy (e.g. fetching a model with all its versions, configurations, and setups in one response).openapi.yaml— The canonical OpenAPI 3.0 spec (243 operations). Served unmodified by Swagger UI. On startup,app.tsstrips response and request body schemas before registering routes to avoid AJV compilation overhead (~30s -> ~2s startup).
Several resource types (models, empiricalmodels, hybridmodels, emulators, etc.) share the modelcatalog_software Hasura table and are distinguished by a type column containing the OWL class URI. The getSoftwareTypeFilter function in service.ts appends the appropriate where: { type: { _eq: ... } } clause automatically.
All resource IDs are full URIs (e.g. https://w3id.org/okn/i/mint/<uuid>). The API accepts plain short IDs and prepends the prefix automatically. New resources created via POST get a randomUUID()-based URI.
To regenerate TypeScript types from the live Hasura schema:
HASURA_GRAPHQL_URL=http://localhost:8080/v1/graphql \
HASURA_ADMIN_SECRET=myadminsecretkey \
npm run codegenOutput goes to src/generated/graphql.ts.