Niels Segers

Full Stack Developer at delta.app


FaunaDB, GraphQL, Next.js and my newborn baby

With my son's birth coming up we felt like we wanted a modern aproach to a Birth Card and wanted to make a website. The idea was simple. A website with a landing page, information for everything people might need (birth list, etc.) and an area where people could leave messages.

I wanted to experiment with some technologies I didn't have that much experience in. So I started thinking which ones I wanted to use. People need to be able to login with something like facebook or twitter: oauth. People's messages need to be stored: graphql.

So I started building an entire apollo-express server (GraphQL with an underlying MariaDB database) with support for users and messages and of course basic authentication through JWT tokens.

Fuck my life... talk about overengeneering such a simple thing. I could already imagine myself in the hospital troubleshooting while my girlfrield is giving birth...

- "Oh shit the API went down! People are trying to leave messages but are unable to. Let me fix this real quick. Don't give birth just yet!".

I think she would kill me if I pulled of something like that.

So I took a step back and thought how I could simplify the entire stack. I remember @zeithq recently promoting new and upcoming technologies regarding backendlessconf sponsoring. One of them caught my eye: FaunaDB.

I'm already an incredible serverless enthousiast and host most of my websites through @zeithq's now already. So if something new pops up I'm keen to try it out. FaunaDB is a serverless database where you pay based on usage. And the biggest plus? They have a free plan! As long as you stay below 100K read ops and 50K write ops per day you don't pay anything. Ideal for something relatively small! So if I handle this correctly I should be able to host my database for free without having to deal with all the hassle surrounding it.

So instead of setting up the entire database and graphql server I now moved my entire graphql schema to FaunaDB.

After creating a database and importing a graphql schema FaunaDB takes care of everything else. My use case was pretty small so logically my schema is as well.

type Message {
  text: String
  user: User
}

type User {
  username: String
  displayName: String
  avatar: String
}

type Query {
  messages: [Message]
  users: [User]
  findUserByUsername(username: String!): User
}

Importing the above schema auto generated the below final schema.

directive @embedded on OBJECT
directive @collection(name: String!) on OBJECT
directive @index(name: String!) on FIELD_DEFINITION
directive @resolver(
  name: String
  paginated: Boolean! = false
) on FIELD_DEFINITION
directive @relation(name: String) on FIELD_DEFINITION
directive @unique(index: String) on FIELD_DEFINITION
scalar Date

scalar Long


type Message {
  _id: ID!
  _ts: Long!
  text: String
  user: User

}

input MessageInput {
  text: String
  user: MessageUserRelation
}


type MessagePage {
  data: [Message]!
  after: String
  before: String
}

input MessageUserRelation {
  create: UserInput
  connect: ID
  disconnect: Boolean
}


type Mutation {
  updateUser(
    id: ID!
    data: UserInput!
  ): User

  createUser(data: UserInput!): User!
  updateMessage(
    id: ID!
    data: MessageInput!
  ): Message
  createMessage(data: MessageInput!): Message!
  deleteMessage(id: ID!): Message
  deleteUser(id: ID!): User

}


type Query {
  messages(
    _size: Int
    _cursor: String
  ): MessagePage!
  findByUsername(username: String!): User

  users(
    _size: Int
    _cursor: String
  ): UserPage!
  findUserByID(id: ID!): User

  findMessageByID(id: ID!): Message
  findUserByUsername(username: String!): User

}

scalar Time


type User {
  displayName: String
  avatar: String
  username: String
  _id: ID!
  _ts: Long!
}

input UserInput {
  username: String
  displayName: String
  avatar: String
}


type UserPage {
  data: [User]!
  after: String
  before: String
}

As you can see FaunaDB takes care of a lot of the basic stuff for you. There's pagination and a bunch of basic queries and mutations for you to use. After importing your graphql schema you are presented with a default playground.

They also provide you with an easy to read overview to check some basic statistics.

So... the upside? No more maintaining the DB and worrying about load.

The downside? Well... no easy way of implementing custom mutations that could handle oauth for me.

The last one kind of forced me to keep some of the hosting locally since I still need to handle verification. So I wrote a layer inbetween that handles the user authentication where needed and fetches some stuff after verification. eg. We need to check if a user is logged in and valid before it can create a message.

So now the frontend fetches data that doesn't need authentication directly from the hosted graphql database. For data that requires user authentication it communicates through the authentication API which then performs mutations and queries to graphql paired with some custom logic on the server side.

This lead me to the final stack:

  • Frontend in Next.js - hosted on Zeit Now
  • Authentication API (express server) - hosted on a SBC at home
  • GraphQL - hosted on FaunaDB

It's not ideal but a nice enough solution to my problem. If the Authentication API dies the website is still filled with data and users don't immediately notice that something is wrong.

I have to pay attention to the read and write ops per day for FaunaDB though so I don't exceed any limits of the free plan. Maybe if I need to I'll introduce a caching layer on the self hosted API which could save me quite some ops per user (but reintroduces the risk of the site not working at all because all the data goes through the API).

Written by Niels Segers