fireQL
fireQL copied to clipboard
FireQL - GraphQL API for Firestore - Boilerplate plug and play

Warning
🛑🛑 This repository is no longer maintained due to my lack of interest today for this technology. I'm now working on the Napi technology - https://getnapi.com. I added all the sources of the NPM library by the way. Feel free to do whatever you want with it. See you soon! 🛑🛑

FireQL is a GraphQL connector for Firestore (Firebase database). This repository offer a boilerplate to auto-host a GraphQL server on your Firebase Project (hosting part) connecting to Firestore on the same project.
At the moment, use this repository at your own risk, I can't assure the continuity of this project. It's more an experiment for my personnals works than a real technology for en every day use. I'll make it more usable depending of its popularity.
Summary
- # Getting started – Create project, environment, clone repository, initialize, run playground
- # Create a first type
- # Add documents – Adding a mutation to our schema, to our resolvers, execute it
- # Get documents – Adding a query to our schema, to our resolvers, execute it
- # Update documents – Adding a mutation to our schema, to our resolvers, execute it
- # Remove documents – Adding a mutation to our schema, to our resolvers, execute it - (in development)
- # Working with relations – Updating our type, adding inputs, execute fun queries & mutations
- # Query your API from your application
- # What's next?

Getting started
Create your Firebase project & your Firestore database
Note: Free projects (spark) works with FireQL!
No specifical needs here. Just be sure to create a Firestore database and not a realtime one.
Prepare your environment
Execute these commands in order to install firebase CLI and to login with your Firebase account (Obviously, you need one). Just follow steps.
$ npm install -g firebase-tools
$ firebase login
Clone this repository
Git clone it or just download zip.
$ git clone https://github.com/Illuday/fireQL.git
Init Firebase project
Initialise the firebase project :
$ firebase init
# Select "Functions" & "Hosting"
# Choose your previously created project
# Use Javascript
# Say "no" to ESlint (You'll be able to install it later.)
# Don't override index.js & packages.json
# Say Yes to dependencies
# Use public directory
# Don't create SPA
# Done!
Open GraphQL playground with function emulating
Setup your Google credentials : https://firebase.google.com/docs/functions/local-emulator#set_up_admin_credentials_optional
firebase emulators:start
Note: On some systems, emulators doesn't seams to work using this last command. You can downgrade firebase-tools to 6.8.0, then run :
firebase serve
Access your playground on:
http://localhost:5001/YOU_PROJECT_ID/us-central1/api (Given in the console)
Playground is running! You have to copy/paste your api link (url above) in the upper field inside the playground in order to make it work.
Open GraphQL playground on Firebase Hosting
firebase deploy
Then go to Firebase console, section "Functions", you should find your url:
https://us-central1-YOU_PROJECT_ID.cloudfunctions.net/api
Playground is running! You have to copy/paste your api link (url above) in the upper field inside the playground in order to make it work.
Deploy for production
Todo.

Create a first type
For non GQL user: A type is a collection / table in your firestore database.
To create a type, just add it in the schema. You can add as much type you need. We'll come back on relations later.
type Artist {
id: ID
name: String!
age: Int!
}
functions/graphql/types/artistType.graphql

Add document to our type
For non GQL user: Compare to a restAPI, a resolver is a GraphQL "route", a mutation will represent a put/patch route with parameters.
1 - Adding the mutation to our schema
type Mutation {
addArtist(name: String!, age: Int!): Artist
}
functions/graphql/types/artistType.graphql
2 - Adding the mutation to our resolvers
FireQL is my magical library to connect our graphQL server to our firestore. FireQL.add() will automatically add the new artist to our firestore collection "artists".
const resolverFunctions = {
Query: {},
Mutation: {
addArtist: (parent, document) => fireQL.add({ collectionName: 'artists', document }),
},
};
functions/graphql/resolvers.js
3 - Executing mutation
Go to your GQL playground and execute your mutation. There, we want to add an artist named "illuday", and get his id and his name (probably illuday...).
For non GQL user: GraphQL allows you to get only fields you want as result of any queries or mutations.
ADDING AN ARTIST
mutation {
addArtist (name: "illuday", age: "28") {
id
name
}
}
Mutation - Playground
:arrow_down:
{
"data": {
"addArtist": {
"id": "BsuNNpRQqFbgWME1RIZ4",
"name": "illuday"
}
}
}
Mutation result - Playground
In firestore, you can see that you have your document, added to artists collection with "illuday" as name and 28 as age.
Note: We havn't age in result because we didn't ask for it.
Magic.
We'll come back later on adding, with more powerful add!

Get documents
1 - Adding the query to our schema
type Query {
getArtists(where: WhereInput): [Artist]
}
functions/graphql/types/artistType.graphql
The result value of this query is [Artist], it'll return an Array of Artist type.
WhereInput is an helper that provide the structure for querying firestore. The object needed here is:
{
field: 'nameOfYourField'
operator: 'enum: EQ (==), GT (>), GTE (>=), LE (<), LTE (<=), INARRAY'
value: { // One of
intValue: intValue
stringValue: stringValue
}
}
Note: This helper is already provide in your schema from this repository.
2 - Adding the query to our resolvers
FireQL.get() will automatically get artists from our firestore collection "artists".
const resolverFunctions = {
Query: {
getArtists: (parent, { where }) => FireQL.get({ collectionName: 'artists', where }),
},
Mutation: {
...
},
};
functions/graphql/resolvers.js
3 - Executing query
Before executing this query, I seed my database to have more artists.
- illuday: 28y/o
- Anna Dittmann: 26y/o
- Ilya Kuvshinov: 29y/o
- Shayline: 27y/o
Let's make some tries in our GraphQL playground.
Note: I named my queries (in playground) to be able to save them all.
GET ALL ARTISTS
query getAllArtists { # <-- This is just a name for GQL playground
getArtists {
id
name
age
}
}
:arrow_down:
{
"data": {
"getArtists": [
{
"id": "GF0ihzKePxeKZMRTjY7A",
"name": "illuday",
"age": 28
},
{
"id": "NVLWsTYEq6GgqvoCvU6W",
"name": "Shayline",
"age": 27
},
{
"id": "TgI9PYG4p7OKzrOBmzmD",
"name": "Anna Dittmann",
"age": 26
},
{
"id": "mPXsd1tkYRfSxN0UW1aQ",
"name": "Ilya Kuvshinov",
"age": 29
}
]
}
}
GET ARTIST BY ID
query getArtistById { # <-- This is just a name for GQL playground
getArtists (where: { field: "id", value: { stringValue: "TgI9PYG4p7OKzrOBmzmD" } }){
id
name
age
}
}
:arrow_down:
{
"data": {
"getArtists": [
{
"id": "TgI9PYG4p7OKzrOBmzmD",
"name": "Anna Dittmann",
"age": 26
}
]
}
}
GET ARTISTS BY AGE
query getArtistsByAge { # <-- This is just a name for GQL playground
getArtists (where: { field: "age", operator: LT, value: { intValue: 28 } }){
name
age
}
}
:arrow_down:
{
"data": {
"getArtists": [
{
"name": "Anna Dittmann",
"age": 26
},
{
"name": "Shayline",
"age": 27
}
]
}
}

Update document
1 - Adding the mutation to our schema
type Mutation {
...,
updateArtist(id: ID!, name: String, age: Int): Artist
}
functions/graphql/types/artistType.graphql
2 - Adding the mutation to our resolvers
FireQL.update() will automatically update the artist in our firestore collection "artists".
const resolverFunctions = {
Query: {
...
},
Mutation: {
...,
updateArtist: (parent, document) => FireQL.update({ collectionName: 'artists', document }),
},
};
functions/graphql/resolvers.js
3 - Executing mutation
Let's say we want to modify "illuday" age.
UPDATE ILLUDAY AGE
mutation updateIlludayAge { # <-- This is just a name for GQL playground
updateArtist(id: "GF0ihzKePxeKZMRTjY7A", age: 38) {
id
name
age
}
}
:arrow_down:
{
"data": {
"updateArtist": {
"id": "GF0ihzKePxeKZMRTjY7A",
"name": "illuday",
"age": 38
}
}
}

Removing document - (in development)
1 - Adding the mutation to our schema
type Mutation {
...,
removeArtist(id: ID!): Artist
}
functions/graphql/types/artistType.graphql
2 - Adding the mutation to our resolvers
FireQL.remove() will automatically remove the artist in our firestore collection "artists".
const resolverFunctions = {
Query: {
...
},
Mutation: {
...,
removeArtist: (parent, document) => FireQL.remove({ collectionName: 'artists', document }),
},
};
functions/graphql/resolvers.js
3 - Executing mutation
REMOVE ILLUDAY
mutation removeIlluday { # <-- This is just a name for GQL playground
removeArtist(id: "GF0ihzKePxeKZMRTjY7A") {
id
}
}
:arrow_down:
{
"data": {
"removeArtist": {
"id": "GF0ihzKePxeKZMRTjY7A"
}
}
}

Working with relations
Note: in FireQL, all relations must be bi-directionnal.
1 - Adding a new type and create a relation
Artists have MANY artworks, artworks have ONE artist.
type Artwork {
id: ID
name: String
artist: Artist
}
type Artist {
id: ID
name: String!
age: Int!
artworks: [Artwork]
}
functions/graphql/types/artistType.graphql & types/artworkType.graphql
Follow steps above to create basics queries & mutations for the new type
2 - Modifying Artwork mutation to handle relation management
We need to create our inputs before modifying our addArtist & updateArtist mutations. They'll allows those things:
- Add artworks when we add an artist
- Update / Remove artworks when we update artist
input ArtworkInput {
name: String
}
input AddArtworkInput {
collection: String = "artworks"
on: String = "artist"
connect: ID
create: ArtworkInput
}
input UpdateArtworkInput {
collection: String = "artworks"
on: String = "artist"
connect: ID
remove: ID
create: ArtworkInput
}
functions/graphql/types/artworkType.graphql
Back to these inputs:
-
ArtworkInput: Represent fields we can fill when we create an artwork
-
AddArtworkInput:
Argument Value Description collection String = "artworks" Name of the collection linked, set artworks by default. You'll never have to change that. on String = "artist" Foreign field for our relation, set artwork by default. You'll never have to change that. Note: In case of a One to Many or Many to Many relations you'll have to write [String] = ['artists']. connect* ID The id of artwork you want to connect with. create* ArtworkInput The input of artwork you want to create then link. *one of these must be fill when you execute the mutation
-
UpdateArtworkInput:
Argument Value Description collection String = "artworks" Name of the collection linked, set artworks by default. You'll never have to change that. on String = "artist" Foreign field for our relation, set artwork by default. You'll never have to change that. Note: In case of a One to Many or Many to Many relations you'll have to write [String] = ['artists']. connect* ID The id of an artwork you want to connect with. remove* ID The id of an artwork you want to remove. Note: In case of a Many to One or One to one relations, the artwork will be removed from the database create* ArtworkInput The input of artwork you want to create then link. *one of these must be fill when you execute the mutation
Now that we have inputs, we can adjust our mutations "addArtist" and "updateArtist".
type Mutation {
addArtist(name: String!, age: Int!, artworks: [AddArtworkInput]): Artist
updateArtist(id: ID!, name: String, age: Int, artworks: [UpdateArtworkInput]): Artist
...
}
functions/graphql/types/artistType.graphql
That's it! Let's play with it.
3 - Executing mutations
ADD AN ARTIST AND CREATE ARTWORKS AT THE SAME TIME
mutation addAnArtistWithArtworks {
addArtist(
name: "illuday",
age: 28,
artworks: [
{ create: { name: "MIRAMARKA" } }, # NEW ARTWORK
{ create: { name: "BLACKLIST" } }, # NEW ARTWORK
{ connect: "WDwGd4LwjZGfsFEALOi7" } # EXISTING ARTWORK
]
) {
id
name
artworks { id name } # WAIT WHAT ?
}
}
:arrow_down:
{
"data": {
"addArtist": {
"id": "xyseptoQ7WBBRr8XAl4U",
"name": "illuday",
"artworks": [
{
"id": "NmgdsLrNgzIiIUaRFkef",
"name": "MIRAMARKA"
},
{
"id": "W7rgj1Lkc4HAruuMMmX2",
"name": "BLACKLIST"
},
{
"id": "WDwGd4LwjZGfsFEALOi7",
"name": "NYNDOR"
}
]
}
}
}
So, what happens there ? We inserted an new artist in our database, named illuday, we decided to create at the same time two new artworks and connect an already existing one. References between the artist and artworks are automatically set by FireQL.
And the result ? You can see that as we added relations in our artist types, we can query directly its artworks on results (queries, mutations). Yeah!
UPDATE AN ARTIST AND REMOVE ONE ARTWORK
Note: Due to a Firebase limitation (arrayUnion / arrayRemove), you can't add and remove at the same time.
mutation updateAnArtistWithArtworks {
updateArtist(
id: "xyseptoQ7WBBRr8XAl4U", # illuday
artworks: [
{ remove: "W7rgj1Lkc4HAruuMMmX2" }, # BLACKLIST
]
) {
id
name
artworks { id name }
}
}
:arrow_down:
{
"data": {
"updateArtist": {
"id": "xyseptoQ7WBBRr8XAl4U",
"name": "illuday",
"artworks": [
{
"id": "NmgdsLrNgzIiIUaRFkef",
"name": "MIRAMARKA"
},
{
"id": "WDwGd4LwjZGfsFEALOi7",
"name": "NYNDOR"
}
]
}
}
}
QUERYING OUR FINAL ARTIST
query getIlluday {
getArtists(where: {field: "id", value: {stringValue: "xyseptoQ7WBBRr8XAl4U"}}) {
id
name
age
artworks {
id
name
}
}
}
:arrow_down:
{
"data": {
"getArtists": [
{
"id": "xyseptoQ7WBBRr8XAl4U",
"name": "illuday",
"age": 28,
"artworks": [
{
"id": "NmgdsLrNgzIiIUaRFkef",
"name": "MIRAMARKA"
},
{
"id": "WDwGd4LwjZGfsFEALOi7",
"name": "NYNDOR"
},
{
"id": "nTTtND8HyktG30IQzO5M",
"name": "ACTIVITOUR"
}
]
}
]
}
}

Query your API from your application
You just have to use a GQL client (it's like an axios for restAPI), here are some :
- Flutter: https://github.com/zino-app/graphql-flutter
- VueJS: https://github.com/vuejs/vue-apollo
- Nuxt: https://github.com/nuxt-community/apollo-module
- ReactJS: https://github.com/apollographql/react-apollo
- React Native: https://github.com/apollographql/apollo-client
Or for React, Angular, Vue, Ember, Web Components, Meteor, Blaze, Vanilla JS, Next.js and I assume every javascript based framework: https://github.com/apollographql/apollo-client

What's next?
- [x] Connection to Firestore
- [x] Hosting on Firebase cloud functions
- [x] Playgrounds local & online
- [x] Get documents
- [x] Get documents with their relations
- [x] Add documents
- [x] Add documents and relation (create, connect)
- [x] Update documents
- [x] Update documents and their relations (create, connect, remove)
- [ ] Remove documents
- [ ] Remove documents and their relations
- [ ] Handle authentication (Anonymous, phone, e-mail, Facebook, Google)
- [ ] Simple security rules for queries & mutations
- [ ] Subscriptions (for realtime data)
- [ ] File upload to Firebase storage