Introduction to api.serlo.org - serlo/documentation GitHub Wiki

General description of Apollo

Find the documentation of this service here: Official Apollo Server documentation

We decided to use Apollo Server for the following reasons:

  • Apollo Server is the most used GraphQL server for TypeScript. Using TypeScript is a natural choice since we use TypeScript already for a lot of other projects. Thus no extension of the tech stack was necessary.
  • Apollo Server is easy to use (especially for newcomers).

Requirements

Before you start reading this document you should already have a basic understanding about the Apollo Server and GraphQL. The following sites are a perfect introduction:

Note: Apollo Server documents another way to implement data sources as we do. We have decided to use our own implementation in order to have a better control over the cache handling.

The API in Serlo's software architecture in a nut shell

The job of api.serlo.org is to provide a unified API to Serlo which clients like our frontend (frontend.serlo.org) or bots can use to access or modify our (learning) content. Thereby clients access the API via GraphQL.

api.serlo.org uses different other APIs of services. Such services are for example the database layer (which gives an API to our database) or the Google Spreadsheet API (we store the IDs of donors in a special spreadsheet). So api.serlo.org unifies the APIs of all used services into a single one. Therefore clients of Serlo only need to deal with api.serlo.org:

Software architecture of Serlo

An example of a GraphQL endpoint

The way clients deal with the API is via the endpoints the API exposes. Clients can send requests to these endpoints. The starting point of each GraphQL request is either a query (reading / getting something) or a mutation (writing / changing something). An example for a query endpoint is uuid:

type Query {
  uuid(alias: { instance: Instance, path: String }): AbstractUuid
}

enum Instance {
  en,
  de,
  ...
}

Given a resource by specifying the path of the URL and the instance (= subdomain) of the URL it returns an AbstractUuid object. AbstractUuid might be an article, a course, a user or some other objects which can be rendered individually at our site. Here AbstractUuid is an interface type which other types like Article, Course, etc implement:

interface AbstractUuid {
  id: Int!
  ...
}

type Article implements AbstractUuid {
  id: Int!
  currentRevision: ArticleRevision
  ...
}

type ArticleRevision implements AbstractUuid {
  id: Int!
  title: String!
  content: String!
  ...
}

Let's say we want to request the current title and content of the page https://en.serlo.org/community/41297/multiplying-decimal-fractions. From the URL we can deduce the instance (= subdomain) and the path:

                                         path 
                    ----------------------------------------------
https://en.serlo.org/community/41297/multiplying-decimal-fractions
        --
      instance

Thus we can make the request

query {
  uuid(
    alias: {
      instance: en
      path: "/community/41297/multiplying-decimal-fractions"
    }
  ) {
    ... on Article {
      currentRevision {
        title
        content
      }
    }
  }
}

This results in a response with the current title and content:

{
  "data": {
    "uuid": {
      "currentRevision": {
        "title": "Multiplying decimal fractions",
        "content": "[[{\"col\":24,\"content\":\"## Procedure\"}],[{\"col\":16,\"content\":\"1. Ignore ..."
      }
    }
  }
}

You can test the request by yourself at https://api.serlo.org/___graphql

The next article in this series is this one about the architecture of the type system.