Basiliq
Exposing a Postgres database via a REST API that follows the JSON:API specs.
All in all, a tasty API.
- [What is Basiliq](#what-is-basiliq)
- [Quickstart](#quickstart)
- [Ready to use example](#ready-to-use-example)
- [Running locally](#running-locally)
- [Understanding the API](#understanding-the-api)
- [How to query](#how-to-query)
- [Example requests](#example-requests)
- [The configuration](#the-configuration)
- [Generating](#generating)
- [What's in there](#whats-in-there)
- [Checking the configuration](#checking-the-configuration)
- [Testing](#testing)
## What is Basiliq
Basiliq is a **very alpha** REST API that replaces the need to write CRUD methods by exposing a standardized API to interact with a [Postgres](https://www.postgresql.org/) database
It follows the established [JSON:API](https://jsonapi.org/format/)
specifications. Written in [Rust](https://www.rust-lang.org/fr), it tries to conciliate performance with stability.
## Quickstart
### Ready to use example
You could try out the API already deployed on [Heroku](https://www.heroku.com/).
For instance:
```sh
# For a very simple example
curl 'http://demo.basiliq.io/public__peoples'
# For a more complex example
curl 'http://demo.basiliq.io/public__peoples?include=public__articles,public__comments&fields\[public__comments\]='
```
### Running locally
One can install _basiliq_ through docker. An example `docker-compose` script is available at the root of the repository. To start it:
```sh
# To start
docker-compose -f docker-compose.example.yml up -d
# At that point the database is empty,
# you can populate it with the following script
curl -L https://gitlab.com/basiliqio/basiliq_db_test_utils/-/jobs/artifacts/main/raw/basiliq_test.dump\?job\=pack_test_migrations | PGHOST=localhost PGUSER=postgres PGPASS=postgres psql
# Then you can restart the basiliq server so it rescans the database
docker-compose -f docker-compose.example.yml restart basiliq
# To stop
docker-compose -f docker-compose.example.yml down
```
## Understanding the API
### How to query
In the future, there should be a way to generate an [OpenApi](https://swagger.io/specification/) document to view exactly how the _API_ is accessible.
The _API_ response follows the [JSON:API specifications](https://jsonapi.org/format/).
By default, the endpoints are exposed in the format `schema__table`
(i.e. for a table `peoples` in the `public` schema, the endpoint would be `public__table`).
By modifying the configuration one could change how those endpoints are exposed.
### Example requests
Creating request
Notice the lack of id in the request.
Also, in the response, the fields that were not specified are set to their default
```http
POST /public__peoples HTTP/1.1
Host: demo.basiliq.io
User-Agent: curl/7.76.1
Content-Type:application/vnd.api+json
Accept: application/json, */*
Content-Length: 174
{
"data": {
"type": "public__peoples",
"attributes": {
"first-name": "Somebody",
"last-name": "Once_told_me_the_world",
"gender": "F",
"twitter": "@allstars"
}
}
}
HTTP/1.1 201 Created
Connection: keep-alive
Content-Type: application/vnd.api+json
Content-Length: 224
Date: Sun, 02 May 2021 20:20:52 GMT
{
"jsonapi": {
"version": "1.0"
},
"data": {
"type": "public__peoples",
"id": "d14e1928-9cae-441c-945d-144ebe6c94c8",
"attributes": {
"age": null,
"first-name": "Somebody",
"gender": "F",
"last-name": "Once_told_me_the_world",
"twitter": "@allstars"
}
}
}
```
Simple fetching request
```http
GET /public__peoples HTTP/1.1
Host: demo.basiliq.io
User-Agent: curl/7.76.1
Accept: application/json, */*
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/vnd.api+json
Content-Length: 598
Date: Sun, 02 May 2021 20:13:47 GMT
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "public__peoples",
"id": "1649b1e9-8a5f-4f52-b331-c07ce3bccc6f",
"attributes": {
"age": 22,
"first-name": "Francis",
"gender": "M",
"last-name": "Le Roy",
"twitter": null
}
},
{
"type": "public__peoples",
"id": "777cc565-c66b-4942-ab44-8fc5f194b804",
"attributes": {
"age": 34,
"first-name": "Somebody",
"gender": "F",
"last-name": "Wuhu",
"twitter": "@randomhandle"
}
},
{
"type": "public__peoples",
"id": "961e543a-4b22-4d48-a8e5-c1eafada950f",
"attributes": {
"age": null,
"first-name": "AAAAAAAA",
"gender": null,
"last-name": "BBBBBBBBB",
"twitter": null
}
}
]
}
```
Fetching requests including relationships and sparsing fields
You can find the attributes of the objects in the `relationships` key of each main resource in the `included` key below.
Notice that the comments object have only `id`s, because none of their fields was selected via the `fields[public__comments]=` query parameter.
```http
GET /public__peoples?include=public__articles,public__comments&fields[public__comments]= HTTP/1.1
Host: demo.basiliq.io:80
User-Agent: curl/7.76.1
Accept: application/json, */*
HTTP/1.1 200 OK
content-type: application/vnd.api+json
content-length: 1879
date: Sun, 02 May 2021 20:08:12 GMT
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "public__peoples",
"id": "1649b1e9-8a5f-4f52-b331-c07ce3bccc6f",
"attributes": {
"age": 22,
"first-name": "Francis",
"gender": "M",
"last-name": "Le Roy",
"twitter": null
},
"relationships": {
"public__articles": {
"data": [
{
"type": "public__articles",
"id": "fdf715dd-8772-498c-8196-6f4ccb64edef"
},
{
"type": "public__articles",
"id": "2dbf5d1a-b029-4456-af6b-339c75b1089c"
}
]
},
"public__comments": {
"data": [
{
"type": "public__comments",
"id": "59f58abd-c9db-4074-9c34-ac33e9c838ce"
},
{
"type": "public__comments",
"id": "c2add83b-6f58-45a2-bf62-3ebc05c46192"
}
]
}
}
},
{
"type": "public__peoples",
"id": "777cc565-c66b-4942-ab44-8fc5f194b804",
"attributes": {
"age": 34,
"first-name": "Somebody",
"gender": "F",
"last-name": "Wuhu",
"twitter": "@randomhandle"
},
"relationships": {
"public__articles": {
"data": {
"type": "public__articles",
"id": "46c4fe50-8c56-4f26-935e-56ccfa496bb5"
}
},
"public__comments": {
"data": {
"type": "public__comments",
"id": "6ae9938f-d490-4707-b138-770c2a52465f"
}
}
}
},
{
"type": "public__peoples",
"id": "961e543a-4b22-4d48-a8e5-c1eafada950f",
"attributes": {
"age": null,
"first-name": "AAAAAAAA",
"gender": null,
"last-name": "BBBBBBBBB",
"twitter": null
}
}
],
"included": [
{
"type": "public__articles",
"id": "2dbf5d1a-b029-4456-af6b-339c75b1089c",
"attributes": {
"body": "Yeah I know ! Right ?!",
"title": "Oh my g**"
}
},
{
"type": "public__articles",
"id": "46c4fe50-8c56-4f26-935e-56ccfa496bb5",
"attributes": {
"body": "They feast on the blood of the departed draw their powers",
"title": "Why devs require sacrifices"
}
},
{
"type": "public__articles",
"id": "fdf715dd-8772-498c-8196-6f4ccb64edef",
"attributes": {
"body": "Yes",
"title": "How to dead"
}
},
{
"type": "public__comments",
"id": "59f58abd-c9db-4074-9c34-ac33e9c838ce"
},
{
"type": "public__comments",
"id": "6ae9938f-d490-4707-b138-770c2a52465f"
},
{
"type": "public__comments",
"id": "c2add83b-6f58-45a2-bf62-3ebc05c46192"
}
]
}
```
Updating request
Notice that attributes that were not included in the `PATCH` request are not nulled.
```http
PATCH /public__peoples/777cc565-c66b-4942-ab44-8fc5f194b804 HTTP/1.1
Host: demo.basiliq.io
User-Agent: curl/7.76.1
Content-Type:application/vnd.api+json
Accept: application/json, */*
Content-Length: 204
{
"data": {
"type": "public__peoples",
"id": "777cc565-c66b-4942-ab44-8fc5f194b804",
"attributes": {
"first-name": "NotTheOriginalFirstName",
"last-name": "NotTheOriginalLastName"
}
}
}
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/vnd.api+json
Content-Length: 260
Date: Sun, 02 May 2021 20:24:50 GMT
{
"jsonapi": {
"version": "1.0"
},
"data": {
"type": "public__peoples",
"id": "777cc565-c66b-4942-ab44-8fc5f194b804",
"attributes": {
"age": 34,
"first-name": "NotTheOriginalFirstName",
"gender": "F",
"last-name": "NotTheOriginalLastName",
"twitter": "@randomhandle"
}
}
}
```
Deleting request
```http
DELETE /public__peoples/777cc565-c66b-4942-ab44-8fc5f194b804 HTTP/1.1
Host: demo.basiliq.io
User-Agent: curl/7.76.1
Accept: application/json, */*
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/vnd.api+json
Content-Length: 41
Date: Sun, 02 May 2021 20:25:54 GMT
{
"jsonapi": {
"version": "1.0"
},
"data": null
}
```
## The configuration
### Generating
Typically, one would first need to create a configuration, however this is
not mandatory to run _basiliq_.
To create a configuration, one can use :
```sh
basiliq config generate
```
It would generate a file called `basiliq_config.yaml` in the current working directory.
### What's in there
This file would look like the following :
```yml
---
resources: # The list of resources
public__articles: # The name of a resource. *It can be changed*
target: # The identifier object of the resource
schema: public # The schema this resource is bound to in the database
table: articles # The name of the table bound to this resource
enabled: true # `true` if this resource is enabled
relationships: # A list of relationships
public__comments: # Name of the relationship. *It can be changed*
target: # The identifier object of the resource
schema: public # The schema this relationship's resource is bound to in the database
table: comments # The name of the table bound to this relationship's resource
enabled: true # `true` if this relationship is enabled
field: article # The field to which this relationship is bound
public__peoples:
target:
schema: public
table: peoples
through: # Identifies the bucket table for Many-to-Many relationships
schema: public # The schema of the bucket resource
table: comments # The table of the bucket resource
field: author # The field linking the relationship's resource and the bucket resource
enabled: true
field: id
[...]
```
### Checking the configuration
After having generated the configuration, one might need to ensure it's correct after having modified it.
One could do that with the following command:
```sh
basiliq config check --input basiliq_config.yaml
```
## Testing
To test this crate, you need to start a `Postgres` database and export the `DATABASE_URL` environment variable.
You can use the provided `docker-compose` plan
```sh
# To start the test database
docker-compose -f docker-compose.testing.yml up -d
# Don't forget to set the environment variable
export DATABASE_URL="postgres://postgres:postgres@localhost/postgres"
# Run the tests
cargo test
# To stop the test database
docker-compose -f docker-compose.testing.yml down -v
```