/sangria-scalaio-demo

A demo project used in my scala.io talk.

Primary LanguageScalaApache License 2.0Apache-2.0

Sangria Scala.io 2018 Demo

Build Status

A demo project that demonstrates a GraphQL API implemented with sangria, akka-http, circe.

Presentation slides

Presentation slides

Getting Started

After starting the server with

sbt run

# or, if you want to watch the source code changes
 
sbt ~reStart

You can run queries interactively using graphql-playground by opening http://localhost:8080 in a browser or query the /graphql endpoint directly:

GraphQL Playground

Code Structure

This demo contains several packages under src/main/scala:

  • common - common definitions that are used by all examples. It also contains some plumbing (like full HTTP routing) to make more advanced examples more simple.
  • demos - step by step walkthrough from the most basic examples to more advanced GraphQL servers that use DB, auth, etc. Every demo object (like Demo1Basics) is self-contained and contains main method (extends App), so you can run it.
  • finalServer - the final server implementation that includes all demonstrated elements
  • model - defines model for the Book and Author case classes as well as repositories (including SQL-based implementation)

I would also recommend to explore the /src/test/scala folder - it contains several example tests.

Step-by-Step Walkthrough

In addition to the final server implantation the project contains step-by-step demonstration of various GraphQL and Sangria concepts. Every step is self-contained and builds up on top of previous steps:

  1. Most basic example of GraphQL Schema definition and query execution (Demo1Basics.scala)
  2. Using deriveObjectType to derive GraphQL object type based on the Book case class (Demo2MacroDerivation.scala)
  3. Exposing the GraphQL schema via HTTP API (Demo3ExposeGraphQLViaHttp.scala)
  4. Use an SQL database to load the book and author data returned by GraphQL API (Demo4AddingDatabase.scala)
  5. Use field arguments to provide pagination, sorting and filtering (Demo5PaginationSortingFiltering.scala)
  6. Representing book-author relation with an object type field (Demo6BookAuthorRelation.scala)
  7. Efficiently load author information with Fetch API (Demo7UsingFetchers.scala)
  8. Efficiently load author books information with Fetch API (Demo8FetchAuthorBooksRelation.scala)
  9. Guard GraphQL API from abuse with static query complexity analysis (Demo9QueryComplexityAnalysis.scala)
  10. Securing GraphQL API with OAuth and JWT tokens (Demo10Auth.scala)

In each class you will find comments that highlight separate steps (with // STEP: ...) and things that are new in comparison with previous steps (with // NEW: ...). If you add extra highlighting in the IDE (e.g. TODO highlighting in Intellij IDEA) you can get additional visual hints:

TODO highlighting in Intellij IDEA

Demo Database

The demo uses a simple H2 in-memory database (it would be re-created automatically when server starts).

DB schema DDL
create table "BOOKS" (
  "BOOK_ID" VARCHAR NOT NULL PRIMARY KEY,
  "TITLE" VARCHAR NOT NULL,
  "AUTHOR_ID" VARCHAR NOT NULL,
  "description" VARCHAR)
  
alter table "BOOKS" 
  add constraint "AUTHOR_FK" foreign key("AUTHOR_ID") 
  references "AUTHORS"("AUTHOR_ID") on update NO ACTION on delete NO ACTION
  
create table "AUTHORS" (
  "AUTHOR_ID" VARCHAR NOT NULL PRIMARY KEY,
  "NAME" VARCHAR NOT NULL,
  "BIO" VARCHAR,
  "BIRTH_DATE" DATE NOT NULL,
  "DEATH_DATE" DATE)

The example book data was taken from Open Library.

JWT Auth

In order to demonstrate the authentication and authorization, demo uses JWT tokens with basic OAuth bearer Authorization header.

If you would like to try out the examples (in particular me field), you can use following header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6IkpvaG4gRG9lIiwiYm9va3MiOlsiT0wzMDMxMFciLCJPTDk5ODQzVyJdfQ.ffqCpfgWrY40k8JWj56mUpvW0ZfWLhTqrLHwMZeXgXc

GraphQL Schema

The demo project implements following schema:

type Author {
  id: String!
  name: String!
  bio: String
  birthDate: Date!
  deathDate: Date
  books: [Book!]!
}

type Book {
  id: String!
  title: String!
  description: String
  author: Author
}

input BookInput {
  id: String!
  title: String!
  description: String
  authorId: String!
}

enum BookSorting {
  Title
  Id
}

"Represents local date. Serialized as ISO-formatted string."
scalar Date

type Me {
  "The name of authenticated user"
  name: String!
  favouriteBooks: [Book!]!
}

type Mutation {
  addBook(book: BookInput!): Book
  deleteBook(id: ID!): Book
}

type Query {
  "Gives the list of books sorted and filtered based on the arguments"
  books(limit: Int = 5, offset: Int = 0, sortBy: BookSorting, title: String): [Book!]!

  "Returns a book with a specified ID."
  book(id: ID!): Book
  authors(limit: Int = 5, offset: Int = 0): [Author!]!
  author(id: ID!): Author

  "Information about authenticated user. Requires OAuth token."
  me: Me
}