This repository features a simple REST API, inspired by the go REST API tutorial
A few tools can be useful to build, run, and test this application:
- go 1.19 to buil the sources;
- docker and docker-compose to run the stack in containers;
- curl to consume / test our API endpoints;
- jq to get nicely formatted json responses.
The relational DB system chosen for this solution is postgresql, run as a docker container.
In order not to re-invent the wheel, a little help from the great golang open-source community is always useful. Here's the dependencies I used to implement this API (unexhaustive):
- gin-gonic - A powerful http web framework to easily write and maintain REST endpoints;
- gorm - The best solution I found to interact with a postgresql instance and generate the relational schema used in this solution;
- dockertest - Because end-to-end integration is far better than mocking to validate interactions with the RDBMS. Dockertest is used to run a postgresql container for each test suite;
- viper - Viper is used to inject per-environment configuration;
- faker - Integrations tests require data provisioning, faker fits perfectly for this purpose.
Here's the relational schema used to store the API data, entirely defined with Gorm entities as declared in the model package and persisted on server startup with auto migrate feature.
The compose file contained in this repository features two services:
- db: the postgresql db container;
- server: the Go API server container, based on the root Dockerfile of this project.
To get the stack up and running, simply run:
➜ ~ docker-compose up
If you wish to start the application server in Go native mode, simply:
➜ ~ ENV=dev go run ./cmd/server
Note that the ENV must be set to load a specific config file to interact with the database outside the docker created subnet. Obviously, a postgresql instance must be running in order to get a functional API server.
GET /artist - Returns a list of artists.
Query parameters:
offset: mandatory - Sets the offset in the select query;limit: mandatory - Set the fetched records limit in the select query;query: optional - A free text field compared to thenamecolumn.
➜ ~ curl -s http://127.0.0.1:8080/artist\?offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T09:54:57.562Z",
"UpdatedAt": "2022-10-03T09:54:59.128Z",
"DeletedAt": null,
"Name": "Artist One",
"Tracks": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T09:54:57.562Z",
"UpdatedAt": "2022-10-03T09:54:59.128Z",
"DeletedAt": null,
"Name": "Artist Two",
"Tracks": null
}
]
➜ ~ curl -s http://127.0.0.1:8080/artist\?query\=One\&offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T09:54:57.562Z",
"UpdatedAt": "2022-10-03T09:54:59.128Z",
"DeletedAt": null,
"Name": "Artist One",
"Tracks": null
}
]
GET /artist/:id - Returns a single artist with a collection of albums, based on its unique id
{
"ID": 1,
"CreatedAt": "2022-10-03T09:54:57.562Z",
"UpdatedAt": "2022-10-03T09:54:59.128Z",
"DeletedAt": null,
"Name": "Artist One",
"Tracks": [
{
"ID": 1,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track One",
"ArtistID": 1,
"Users": null,
"Albums": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track Two",
"ArtistID": 1,
"Users": null,
"Albums": null
}
]
}
GET /album - Returns a list of albums.
Query parameters:
offset: mandatory - Sets the offset in the select query;limit: mandatory - Set the fetched records limit in the select query;query: optional - A free text field compared to thenamecolumn.
➜ ~ curl -s http://127.0.0.1:8080/album\?offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T10:40:35.447Z",
"UpdatedAt": "2022-10-03T10:40:36.855Z",
"DeletedAt": null,
"Title": "Album One",
"ReleaseYear": 2000,
"Tracks": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T10:40:35.447Z",
"UpdatedAt": "2022-10-03T10:40:36.855Z",
"DeletedAt": null,
"Title": "Album Two",
"ReleaseYear": 2001,
"Tracks": null
}
]
➜ ~ curl -s http://127.0.0.1:8080/album\?query\=One\&offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T10:40:35.447Z",
"UpdatedAt": "2022-10-03T10:40:36.855Z",
"DeletedAt": null,
"Title": "Album One",
"ReleaseYear": 2000,
"Tracks": null
}
]
GET /album/:id - Returns a single album with a collection of tracks, based on its unique id
➜ ~ curl -s http://127.0.0.1:8080/album/1 | jq
{
"ID": 1,
"CreatedAt": "2022-10-03T10:40:35.447Z",
"UpdatedAt": "2022-10-03T10:40:36.855Z",
"DeletedAt": null,
"Title": "Album One",
"ReleaseYear": 2000,
"Tracks": [
{
"ID": 1,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track One",
"ArtistID": 1,
"Users": null,
"Albums": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track Two",
"ArtistID": 1,
"Users": null,
"Albums": null
}
]
}
GET /track - Returns a list of tracks.
Query parameters:
offset: mandatory - Sets the offset in the select query;limit: mandatory - Set the fetched records limit in the select query;query: optional - A free text field compared to thenamecolumn.
➜ ~ curl -s http://127.0.0.1:8080/track\?offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track One",
"ArtistID": 1,
"Users": null,
"Albums": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track Two",
"ArtistID": 1,
"Users": null,
"Albums": null
}
]
➜ ~ curl -s http://127.0.0.1:8080/album\?query\=One\&offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T10:40:35.447Z",
"UpdatedAt": "2022-10-03T10:40:36.855Z",
"DeletedAt": null,
"Title": "Album One",
"ReleaseYear": 2000,
"Tracks": null
}
]
GET /track/:id - Returns a single track with a collection of albums, based on its unique id
➜ ~ curl -s http://127.0.0.1:8080/track/1 | jq
{
"ID": 1,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track One",
"ArtistID": 1,
"Users": null,
"Albums": [
{
"ID": 1,
"CreatedAt": "2022-10-03T10:40:35.447Z",
"UpdatedAt": "2022-10-03T10:40:36.855Z",
"DeletedAt": null,
"Title": "Album One",
"ReleaseYear": 2000,
"Tracks": null
}
]
}
GET /user - Returns a list of users.
Query parameters:
offset: mandatory - Sets the offset in the select query;limit: mandatory - Set the fetched records limit in the select query;query: optional - A free text field compared to thenamecolumn.
➜ ~ curl -s http://127.0.0.1:8080/user\?offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T10:50:47.013Z",
"UpdatedAt": "2022-10-03T10:50:48.412Z",
"DeletedAt": null,
"Login": "login1",
"Tracks": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T10:50:47.013Z",
"UpdatedAt": "2022-10-03T10:50:48.412Z",
"DeletedAt": null,
"Login": "login2",
"Tracks": null
}
]
➜ ~ curl -s http://127.0.0.1:8080/user\?query\=1\&offset\=0\&limit\=10 | jq
[
{
"ID": 1,
"CreatedAt": "2022-10-03T10:50:47.013Z",
"UpdatedAt": "2022-10-03T10:50:48.412Z",
"DeletedAt": null,
"Login": "login1",
"Tracks": null
}
]
GET /user/:id - Returns a single track with a collection of liked tracks, based on its unique id
➜ ~ curl -s http://127.0.0.1:8080/user/1 | jq
{
"ID": 1,
"CreatedAt": "2022-10-03T10:50:47.013Z",
"UpdatedAt": "2022-10-03T10:50:48.412Z",
"DeletedAt": null,
"Login": "login1",
"Tracks": [
{
"ID": 1,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track One",
"ArtistID": 1,
"Users": null,
"Albums": null
},
{
"ID": 2,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track Two",
"ArtistID": 1,
"Users": null,
"Albums": null
}
]
}
POST /user/:id/track/:id - Appends a track to a user, and returns the updated user entity
➜ ~ curl -X POST -s http://127.0.0.1:8080/user/1/track/2 | jq
{
"ID": 1,
"CreatedAt": "2022-10-03T10:50:47.013Z",
"UpdatedAt": "2022-10-03T09:05:27.480880658Z",
"DeletedAt": null,
"Login": "login1",
"Tracks": [
{
"ID": 2,
"CreatedAt": "2022-10-03T10:22:55.079Z",
"UpdatedAt": "2022-10-03T10:22:56.58Z",
"DeletedAt": null,
"Title": "Track Two",
"ArtistID": 1,
"Users": null,
"Albums": null
}
]
}
