Skip to main content

Interface

The web-backend provides a GraphQL interface to communicate with it. GraphQL is a query language that provides type safe queries and mutations for the users to read and manipulate data. Additional information about GraphQL can be found here.

REST vs GraphQL

Nowadays, the two most popular way to communicate with backend APIs is REST and GraphQL. GraphQL is more recent and provides a lot of different advantages compared to REST. Probably the most important one is that it is type safe. In a REST API it's not possible to restrict a user to a body format. They can send any type of data they want and even REST helps with a lot of well working conventions it might still be confusing to the users what needs to be sent to the server and what type of data they can expect back. With GraphQL, it's a completely different story. The backend provides types and queries and the client is able to tell what they need.

Besides this major difference there's a lot of other concepts in which they differ. There's a lot of great articles about the comparison. e.g.: REST vs GraphQL.

The reason this technology has been chosen for this particular project is the maturity of the client side libraries. They provide out of the box caching and state management which is a great help in developing a good UI.

Authentication

The authentication happens with the help of JWT tokens. A token is sent in the redirection URL during the login process. There are a few exceptions, but generally this token has to be sent with every subsequent request in the Authorization header. The following format is required:

Authorization: Bearer <token>

Schema

The GraphQL schema can be written down using SDL(Schema Definition language). The following section contains all the available queries and mutations of the application with its description, relevant SDL part and a process diagram of the logic behind it.

Queries

Queries are requests that are meant to read data from the server. While it's technically possible, data should never be modified during the execution of a query. It's the GraphQL equivalent of a GET request.

ledConfig

ledConfig(id: Int!): LEDConfig
type LEDConfig {
id: Int!
name: String!
configText: String!
createdAt: String!
viewCount: Int!
isFavorite: Boolean
tags: [Tag!]!
user: User!
}

img

ledConfigPage

This query is available without authentication

ledConfigPage(tags: [String], page: Int!, size: Int!, sort: SortMethod!): LEDConfigPage
type LEDConfigPage {
ledConfigs: [LEDConfig],
isLast: Boolean
}
enum SortMethod {
RECENT,
POPULAR
TOP_VOTED
}

Returns a collection of LEDConfigs based on the arguments.

  • tags: The config has to be tagged with the specified tags
  • sort: Sorts the LEDConfigs
    • RECENT: sorted by creation date. The latest is the first
    • POPULAR: sorted by view count. Most viewed is first.
    • TOP_VOTED: sorted by vote count. Most voted is first.

The query supports pagination by accepting page and size arguments.

img

ledConfigFavoritesPage

ledConfigFavoritesPage(page: Int!, size: Int!): LEDConfigPage

Returns a collection of LEDConfigs that is favored by the user issuing the query

img

ledDeviceByMacAddress

ledDeviceByMacAddress(macAddress: String!): LEDDevice
type LEDDevice {
id: String
deviceId: String
macAddress: String
configuration: String
user: User
}
enum Status {
ONLINE
OFFLINE
}

Returns a LEDDevice with the given MAC address

ledDevicesUser

ledDevicesUser: [LEDDevice]

Returns a collection of LEDDevices that belongs to the user issuing the query

img

tags

This query is available without authentication

tags: [Tag]
type Tag {
id: String
name: String
}

Returns all the existing tags

img

refreshToken

This query is available without authentication

refreshToken(refreshToken: String): String

Returns a new access token for the user. Expects a valid refresh token

img

refreshSpotifyToken

refreshSpotifyToken: String

Returns a new spotify access token that belongs to the user issuing the query

img

Mutations

Mutations are requests that change the data on the server. They might create, update or delete data. There are no different ways of doing these actions like the HTTP methods. Mutations also respond with some kind of data.There are a few mutations in the application that always respond with a boolean true if no error happens. The reason for that is the response cannot be empty in GraphQL.

createLEDConfig

createLEDConfig(input: CreateLEDConfigInput!): LEDConfig
input CreateLEDConfigInput {
name: String!
configText: String!
hasMusicAction: Boolean!
tags: [String!]!
}

Creates a new LEDConfig in the database. It will belong to the user that issues the mutation. All the tags that don't exist are also created in the process. Returns the created LEDConfig.

img

deleteLEDConfig

deleteLEDConfig(input: DeleteLEDConfigInput!): Boolean!
input DeleteLEDConfigInput {
id: Int!
}

Deletes the LEDConfig with the given ID from the database.

uploadLEDConfig

uploadLEDConfig(input: UploadLEDConfigInput!): Boolean!
input UploadLEDConfigInput {
configText: String
}

Sends a config to the online devices of the user that issues the mutation. Always returns true.

img

syncLEDConfig

syncLEDConfig: Boolean!

Starts music synchronization process on the user's devices. Always returns true.

img

addToFavorites

addToFavorites(input: AddToFavoritesInput!): Boolean!
input AddToFavoritesInput {
ledConfigId: Int!
}

Adds a LEDConfig to the user's favorites. Returns true if the LEDConfig is already in the favorites, true otherwise.

img

removeFromFavorites

removeFromFavorites(input: RemoveFromFavoritesInput!): Boolean!
input RemoveFromFavoritesInput {
ledConfigId: Int!
}

Removes a LEDConfig from the user's favorites. Returns true if the LEDConfig is is not in the favorites, true otherwise.

img

createLEDDevice

createLEDDevice(input: CreateLEDDeviceInput!): LEDDevice!
input CreateLEDDeviceInput {
deviceId: String
}

Creates a LEDDevice connected to the user that issues the mutation. Returns the created LEDDevice.

img

view

view(input: ViewInput!): Boolean!
input ViewInput {
ledConfigId: String
}

Registers a view connection between the LEDConfig in the argument and the user who issued the mutation. Returns false if the view connection already exists, true otherwise.

img

Error handling

During the execution of queries and mutation several different errors might occur. In GraphQL the convention is that the response status should be 200 OK and the response body should have an errors field that has additional information about the error. The application follows this convention.

This is how an error looks like if the refreshToken query is called with an invalid refresh token:

{
"errors": [
{
"message": "Exception while fetching data (/refreshToken) : Invalid
refresh token",
"locations": [
{
"line": 8,
"column": 3
}
],
"path": [
"refreshToken"
],
"extensions": {
"code": "UNAUTHENTICATED",
"classification": "DataFetchingException"
}
}
],
"data": {
"refreshToken": null
}
}

The nature of the error can be determined by the extensions > code field.

The following errors might occur:

  • LEDConfig not found: There is no LEDConfig with the ID in the argument
  • LEDConfig page does not exist: There is not enough LEDConfig in the query result to populate the given page
  • Invalid access token: The access token sent in the request header does not exist or invalid
  • Invalid refresh token: The refresh token provided is not valid