supagraphql
supagraphql copied to clipboard
GraphQL server using Supabase, GraphQL Helix, Envelop and Fastify
supagraphql
supagraphql is an example GraphQL Server implemented using Supabase, GraphQL Helix, Envelop and Fastify.
This example relies on the Supabase sample Countries data being loaded into the countries
table and row-level-security (RLS) to secure mutations.
You can try a demo at https://supagraphql-production.up.railway.app/graphql
Or, if you want your own:
Envelop and GraphQL-Helix
This example uses the following Envelop plugins:
-
useLogger
to log GraphQL lifecycle activity -
useExtendContext
to inject info into context, such as the authenticatiojnaccess_token
JWT -
useGenericAuth
implement a custom authentication flow that checks for the@auth
directive on queries or mutations and a valid Supabase JWT. We'll use this to authenticate operations protected by RLS. -
useMaskedErrors
to prevent sensitive information from leaking in error message responses -
useSchema
to load your GraphQL schema - [
enableInternalTracing
] to inject timing traces for each phase
Setup
- Create new project on Supabase
- Create the Quick Start sample
Countries
data
-
Or, create the
countries
table and data using thesql/countries.sql
script -
Since we want to secure the
countries
data, we'll use Row Level Security (RLS) and create the following policies so that everyone can read (SELECT), but only authenticated users can add (INSERT) or edit (UPDATE) -- and no one can delete:
--
-- Name: countries Enable access to all users; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Enable access to all users" ON public.countries FOR SELECT USING (true);
--
-- Name: countries Enable insert for authenticated users only; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Enable insert for authenticated users only" ON public.countries FOR INSERT WITH CHECK ((auth.role() = 'authenticated'::text));
--
-- Name: countries Enable update for users based auth; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Enable update for users based auth" ON public.countries FOR UPDATE USING ((auth.role() = 'authenticated'::text)) WITH CHECK ((auth.role() = 'authenticated'::text));
--
-- Name: countries; Type: ROW SECURITY; Schema: public; Owner: -
--
ALTER TABLE public.countries ENABLE ROW LEVEL SECURITY;
Note: these policies can be found in
sql.countries_rls.sql
Running the GraphQL Server
- Install all dependencies from the root of the repo (using
yarn
) - Configure
.env
with your Supabase clientSUPABASE_URL
,SUPABASE_KEY
,SUPABASE_JWT_SECRET
from the Supabase API settings -
cd
into that folder, - To generate types, run
yarn gen
. - To start the server, run
yarn start
- Open http://localhost:3000/graphql in your browser, and try to run:
query { hello }
You should get the response back:
{
"data": {
"hello": "Hello!"
}
}
Build and serve
If you deploy this project, you'll need to compile the typescript and build into /dist
:
- To build the typescripts into
dist
, runyarn build
- To serve when deploying, run
yarn serve
Example Queries and Mutations
Queries can be performed by all users.
Mutations on countries are protected by Envelop's generic auth
plugin that checks the Authorization header for a valid, unexpired and verified Bearer token. This is a JWT (JSON Web Token) set as the session, or returns as part of the Sign In.
Note: The
SUPABASE_JWT_SECRET
is needed to verify the JWT.
Get Countries
query COUNTRIES {
countries {
id
name
iso2
}
}
Returns
Get Country
query GET_COUNTRY($id: Int!) {
country(id: $id) {
id
name
iso2
continent
}
}
Variables
{ "id": 234 }
Returns
Update a Country
Note: This Mutation requires an authenticated user, ie a valid Bearer JWT in the Authorization header.
mutation UPDATE_COUNTRY($id: Int!, $input: UpdateCountryInput!) {
updateCountry(id: $id, input: $input) {
name
iso2
local_name
}
}
Variables
{ "id": 1, "input": { "name": "London is a Country", "iso2": "LIAC" } }
Headers
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjM0MzQ0NTU1LCJzdWIiOiI0NWUyOWJiNS00NTA3LTQ0NTktOTFkNC03NDMxNDU0OGUzODkiLCJlbWFpbCI6ImR0aHlyZXNzb24rc2JnM0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.f050nI-sCynXBQ3vSkacUFQsQgumClmFpM5PuKe6hek"
}
Create a Country
Note: This Mutation requires an authenticated user, ie a valid Bearer JWT in the Authorization header.
mutation ADD_COUNTRY($input: CreateCountryInput!) {
createCountry(input: $input) {
id
name
iso2
continent
}
}
Variables
{
"input": {
"name": "In a Big Country",
"iso2": "BIG"
}
}
Headers
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjM0MzQ0NTU1LCJzdWIiOiI0NWUyOWJiNS00NTA3LTQ0NTktOTFkNC03NDMxNDU0OGUzODkiLCJlbWFpbCI6ImR0aHlyZXNzb24rc2JnM0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.f050nI-sCynXBQ3vSkacUFQsQgumClmFpM5PuKe6hek"
}
Delete a Country
Note: This Mutation requires an authenticated user, ie a valid Bearer JWT in the Authorization header.
mutation DELETE_COUNTRY($id: Int!) {
deleteCountry(id: $id) {
id
name
iso2
continent
}
}
Variables
{
"id": 92
}
Headers
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjM0MzQ0NTU1LCJzdWIiOiI0NWUyOWJiNS00NTA3LTQ0NTktOTFkNC03NDMxNDU0OGUzODkiLCJlbWFpbCI6ImR0aHlyZXNzb24rc2JnM0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.f050nI-sCynXBQ3vSkacUFQsQgumClmFpM5PuKe6hek"
}
Sign Up
mutation SIGNUP($email: String!, $password: String!) {
signUp(email: $email, password: $password) {
id
email
}
}
Variables
{ "email": "[email protected]", "password": "12345678" }
Sign In
mutation SIGNIN($email: String!, $password: String!) {
signIn(email: $email, password: $password) {
id
email
access_token
}
}
Variables
{ "email": "[email protected]", "password": "12345678" }
GraphQL Code Generation
The command yarn gen
- Generates types from the SDL in typedefs
- Generates anintrospection schema in json format
You will want to regenerate types when modifying the schema (types or queries/mutations).