Call a GraphQL API
You'll read countries from a public GraphQL API through a contract the build checks, handle the call failing and give a client an API key the application code never sees. The API is https://countries.trevorblades.com/, which needs no key.
Before you start
Install from crates.io. You need no Node and no package manager.
cargo install snapfire_compiler
cargo install snapfire_fsr_cli
fsr --version
Every command and screenshot on this page was captured with fsr 0.x.
Start the app#
The pages here render on the server and place no island, so the app needs no framework.
Write the document#
A GraphQL client is one file, app/clients/<name>.graphql. It holds the schema's SDL followed by the named operations the app sends. You only need the part of the schema your operations reach:
type Query
type Continent
type Country
type Language
query Continent($code: ID!)
query Country($code: ID!)
The file's name is the service's name, so this one is services.countries. Each operation is a method named in lowerCamel, so Country is services.countries.country. Its variables are the arguments.
Point the client at the endpoint#
[]
= "https://countries.trevorblades.com/"base_url is where every operation is posted. The .graphql extension picks the GraphQL transport, which posts the operation with the arguments as its variables and answers the response's data. Build and the report names it:
Read the generated types#
Each answer is typed from the operation's selection, not from the whole schema:
country is nullable because the schema says country(code: ID!): Country. capital is nullable for the same reason. Reading a field the operation didn't select is a type error:
The build also validates every operation against the schema. Misspell capital in the query and it stops there:
List a continent#
;
;
;
The page checks null before it reads a field, since the type says the answer can be null.
Show one country#
;
;
;
Run fsr dev atlas/app and open /. You get Europe with 53 countries. Click France and you get Paris, EUR and French. Open /country/XX and you get a 404 with "no country with the code XX", since the API answers null for a code it doesn't know and the loader turns that into not_found. The page only renders a country that exists.
When the call fails#
A failed call has a kind. The GraphQL transport reads the first error's extensions.code:
extensions.code | Kind |
|---|---|
NOT_FOUND | not_found |
UNAUTHENTICATED | unauthorized |
FORBIDDEN | forbidden |
BAD_USER_INPUT, BAD_REQUEST, GRAPHQL_VALIDATION_FAILED, GRAPHQL_PARSE_FAILED | invalid |
| none or anything else | internal |
A non-2xx answer without errors maps the way it does over HTTP. A backend that doesn't answer at all is unavailable.
Try that last one. Point the client at a port nothing listens on:
[]
= "http://127.0.0.1:3429/"Reload /. The loader failed, so the route's error component renders with a 503. Its message names countries.continent as unavailable.
The loader never saw a connection error. The transport turned it into a kind before the loader's await returned.
To answer the failure yourself, catch it in the loader and read its kind:
;
;
;
With the dead port, / now reads "The atlas is not answering (unavailable). Try again in a minute." Put base_url back when you're done.
Send an API key#
Many GraphQL APIs want a key that belongs to the application, not to a visitor. Declare it under [secrets] and name it on the client:
[]
= "The atlas API key"
[]
= "https://countries.trevorblades.com/"
= { = "atlas", = "x-api-key" }Without header the key goes as Authorization: Bearer <value>. query = "key" sends it as a query parameter instead. The key sits on the client's transport, so no loader, trace or interceptor sees it. There is no argument a loader could pass it in.
The key needs a value or the host stops at boot:
`secrets.atlas.value` has no value and a client sends it; set C5_SECRETS__ATLAS__VALUE or write it encrypted in a configuration file the host reads
A key for your own machine can go in config/development.toml as plain text:
[]
= "a-key-for-this-machine"Outside development a plain-text value in a file stops the host, since that file is in the repository. A deployment sets C5_SECRETS__ATLAS__VALUE instead. fsr doctor tells you what the production ladder still expects:
A client on the mock transport sends nothing, so your tests run with no key at all.
Next up: 380. Embed a video.