Database - Take434/Appollon GitHub Wiki

Setup

Appollon utilizes a MySql database, which is one of the most popular systems for relational databases. Relational meaning, that the database is structured around relations between entities [4].
At first the application used a local database, which was set up using a docker container. After that, the database was deployed on Azure.

Model

The term "Model" has two different meanings in this context. The first one, is a model in reference to a database. Here it refers to the whole database schema, including all entities and their relations. If looked at from a code perspective, the model refers to a type. That type has a name and contains properties, which could be anything from a string to a new object [7].
Appollon uses two different types of models. The API models, which are used to retrieve the correct data from the Spotify API and the database / application models, which are used within the database and along the application.

API Models

The API models are based on Spotify's API. They usually follow a pattern, which is similar across most endpoints. All API models utilize the Zod library, which guarantees type-safety for all http requests. An example for an API model looks as follows:

export const apiPlaylist = z.object({
  id: z.string(),
  name: z.string().nullish(),
  images: z
    .object({
      url: z.string(),
    })
    .array(),
  owner: z.object({
    display_name: z.string(),
  }),
  followers: z.array(apiUser),
  tracks: apiTrack.array(),
});

This is the response object received from the Spotify API when the application queries a single playlist [3]. The playlist object returned from the API actually offers more information, but the properties shown above are the ones, which are needed for Appollon.
When the application queries multiple playlists, which is usually the case, a slightly different approach is used.
First, a request to retrieve basic data about the playlists is emitted. The model that is returned, looks as follows (note that the apiSimplifiedPlaylistObject does not contain any tracks yet) [1]:

const apiPlaylistResponse = z.object({
  limit: z.number(),
  offset: z.number(),
  total: z.number(),
  items: z.array(apiSimplifiedPlaylistObject),
});

The properties limit, offset and total are used to implement pagination. Read more about this on [Spotify Endpoints](Spotify Endpoints).
Secondly, another endpoint gets queried to obtain the tracks of the playlist. The response of that request looks like this:

const apiTracksForPlaylistResponse = z.object({
  limit: z.number(),
  offset: z.number(),
  total: z.number(),
  items: z.array(
    z.object({
      track: apiTrack,
    })
  ),
});

This model even has another object within the items property before the track appears. This is due to the Spotify API, since it returns another object with information about when the track was added to the playlist [2].

DB Models

Furthermore, models from the database are being used. These are automatically generated by the Prisma framework, when you add them to the schema.prisma file. An example of a model in the schema.prisma file looks like this [5]:

model Track {
  id               String           @id
  title            String
  addedTo          Playlist[]
  audio_features   Audio_Features?  @relation(fields: [audio_FeaturesId], references: [id])
  audio_FeaturesId String?          @unique
  artists          Artist[]
}

The ER-Diagram corresponding to the prisma schema would look like this. It shows the entities with their properties, including the type and whether they are a key attribute. Some of these tables are essential to realize the many-to-many relationships. Furthermore, it shows whether an attribute is NN (NOT NULL). The type VARCHAR(191) represents a string and the TEXT type is a string, which has more space allocated.

The diagram is created using the db diagram website

These models can be easily accessed in the code by importing them from the prisma client and type casting them.

const testTrack = {} as Track;

Objects created this way do not contain any nested objects, to counteract this, the types have to be logically connected. Since the application mostly did not need any composited types, they are created whenever used. Another option would be to explicitly add them, so they can be imported from anywhere. An example of this looks like this [6]:

const testTrack = {} as Track & { addedTo: Playlist[]};

Implementing connected types in this way is a rather dirty way of implementing it, because it requires manual changing once the database schema changes. A cleaner approach would involve using the Prisma Validator.

Terminology

ERD An Entity-Relationship-Diagram is used to display the major entities of a database model and relations between them. It is a standard among developers and helps contribute simple information about a data model.

References

[1] Spotify Web API Reference Get Current User's Playlists last accessed 26.07.2023
[2] Spotify Web API Reference Get Playlist Items last accessed 26.07.2023
[3] Spotify Web API Reference Get Playlist last accessed 26.07.2023
[4] MySql Docs Overview last accessed 27.07.2023
[5] Prisma Docs Database Model last accessed 26.07.2023
[6] Prisma Docs Advanced Type-safety last accessed 26.07.2023
[7] Typescript Docs Objects last accessed 31.07.2023

⚠️ **GitHub.com Fallback** ⚠️