Skip to content

manniru/fireQL

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 

Repository files navigation



FireQL is a GraphQL api 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 startedCreate project, environment, clone repository, initialize, run playground
  • # Create a first type
  • # Add documentsAdding a mutation to our schema, to our resolvers, execute it
  • # Get documentsAdding a query to our schema, to our resolvers, execute it
  • # Update documentsAdding a mutation to our schema, to our resolvers, execute it
  • # Remove documentsAdding a mutation to our schema, to our resolvers, execute it - (in development)
  • # Working with relationsUpdating 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

⬇️


{
  "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
  }
}

⬇️


{
  "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
  }
}

⬇️


{
  "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
  }
}

⬇️


{
  "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
  }
}

⬇️


{
  "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
  }
}

⬇️


{
  "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 ?
  }
}

⬇️


{
  "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 }
  }
}

⬇️


{
  "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
    }
  }
}

⬇️


{
  "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 :

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?

  • Connection to Firestore
  • Hosting on Firebase cloud functions
  • Playgrounds local & online
  • Get documents
  • Get documents with their relations
  • Add documents
  • Add documents and relation (create, connect)
  • Update documents
  • 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

You can choose the feature you need the most:

About

FireQL - GraphQL API for Firestore - Boilerplate plug and play

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 100.0%