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:
This example uses the following Envelop plugins:
useLogger
to log GraphQL lifecycle activityuseExtendContext
to inject info into context, such as the authenticatiojnaccess_token
JWTuseGenericAuth
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 responsesuseSchema
to load your GraphQL schema- [
enableInternalTracing
] to inject timing traces for each phase
- 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
- 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!"
}
}
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
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.
query COUNTRIES {
countries {
id
name
iso2
}
}
query GET_COUNTRY($id: Int!) {
country(id: $id) {
id
name
iso2
continent
}
}
{ "id": 234 }
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
}
}
{ "id": 1, "input": { "name": "London is a Country", "iso2": "LIAC" } }
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjM0MzQ0NTU1LCJzdWIiOiI0NWUyOWJiNS00NTA3LTQ0NTktOTFkNC03NDMxNDU0OGUzODkiLCJlbWFpbCI6ImR0aHlyZXNzb24rc2JnM0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.f050nI-sCynXBQ3vSkacUFQsQgumClmFpM5PuKe6hek"
}
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
}
}
{
"input": {
"name": "In a Big Country",
"iso2": "BIG"
}
}
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjM0MzQ0NTU1LCJzdWIiOiI0NWUyOWJiNS00NTA3LTQ0NTktOTFkNC03NDMxNDU0OGUzODkiLCJlbWFpbCI6ImR0aHlyZXNzb24rc2JnM0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.f050nI-sCynXBQ3vSkacUFQsQgumClmFpM5PuKe6hek"
}
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
}
}
{
"id": 92
}
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjM0MzQ0NTU1LCJzdWIiOiI0NWUyOWJiNS00NTA3LTQ0NTktOTFkNC03NDMxNDU0OGUzODkiLCJlbWFpbCI6ImR0aHlyZXNzb24rc2JnM0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.f050nI-sCynXBQ3vSkacUFQsQgumClmFpM5PuKe6hek"
}
mutation SIGNUP($email: String!, $password: String!) {
signUp(email: $email, password: $password) {
id
email
}
}
{ "email": "someone@example.com", "password": "12345678" }
mutation SIGNIN($email: String!, $password: String!) {
signIn(email: $email, password: $password) {
id
email
access_token
}
}
{ "email": "someone@example.com", "password": "12345678" }
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).