This is a demo application which showcases how to create a simple CRUD REST service backed by FaunaDB. The examples presented in the Getting Started and CRUD guides have been used as starting point for implementing the service.
Table of Contents
Create a Fauna Cloud account filling the form here.
Open a terminal and install the Fauna Shell. If you are on a PC, you can use npm:
$ npm install -g fauna-shell
Alternatively, if you are on a Mac, you can use Homebrew:
$ brew install fauna-shell
Next, create a new database to which the application will connect to for storing the data. For doing so, login to your Fauna Cloud account from the Fauna Shell by typing the following command:
$ fauna cloud-login
It will prompt for your Fauna Cloud credentials, where you need to enter the email you used for signing up, and your password.
Email: [email protected]
Password: **********
Note: once you are logged in on a machine, the Fauna Shell will remember your credentials so you don’t need to log in again.
The next step will be to create the database. Issue the following command for creating a database called demo-app
:
$ fauna create-database demo-app
Run below command for creating the DB schema. It will execute all required queries from a file.
$ fauna run-queries demo-app --file=./scripts/create_schema.fql
Next, issue an API Key for connecting to the new created DB from the service. Execute below command for doing so:
$ fauna create-key demo-app server
Make sure to write down the given secret key. It will be used for starting up the service later on.
Alternatively, you can also create an API Key from the Cloud Dashboard here.
For starting up the service, execute below command making sure to include a proper API key as follows:
$ ./mvnw -Dfauna-db.secret=your_api_key_goes_here spring-boot:run
The app will start by default at port 8080
.
Note: the Maven Wrapper has been added to the project which allows Maven commands to be executed out of the box. Altervaintely, you can use any other Maven installation of your choice.
Creates a new Post with an autogenerated Id.
POST /posts
Content-type: application/json
{
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
}
$ curl -XPOST -H "Content-type: application/json" -d '{
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
}' 'http:https://localhost:8080/posts'
Status: 201 - Created
Content-type: application/json
{
"id": "219871526709625348",
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
}
Creates several Posts within a single requests. The Posts will be persisted in a single transaction as well.
POST /posts
Content-type: application/json
[
{"title": "My cat and other marvels", "tags": ["pet", "cute"]},
{"title": "Pondering during a commute", "tags": ["commuting"]},
{"title": "Deep meanings in a latte", "tags": ["coffee"]}
]
$ curl -XPOST -H "Content-type: application/json" -d '[
{"title": "My cat and other marvels", "tags": ["pet", "cute"]},
{"title": "Pondering during a commute", "tags": ["commuting"]},
{"title": "Deep meanings in a latte", "tags": ["coffee"]}
]' 'http:https://localhost:8080/posts'
Status: 200 - OK
Content-type: application/json
[
{
"id": "219970669169869319",
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
},
{
"id": "219970865138237959",
"title": "Pondering during a commute",
"tags": ["commuting"]
},
{
"id": "219970873639043587",
"title": "Deep meanings in a latte",
"tags": ["coffee"]
}
]
Retrieves an existent Post for the given Id. If the Post cannot be found, a 404 - Not Found
response is returned.
GET /posts/{post_id}
$ curl -XGET 'http:https://localhost:8080/posts/219871526709625348'
Status: 200 - OK
Content-type: application/json
{
"id": "219871526709625348",
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
}
Retrieves all existent Posts.
GET /posts
$ curl -XGET 'http:https://localhost:8080/posts'
Status: 200 - OK
Content-type: application/json
[
{
"id": "219970669169869319",
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
},
{
"id": "219970865138237959",
"title": "Pondering during a commute",
"tags": ["commuting"]
},
{
"id": "219970873639043587",
"title": "Deep meanings in a latte",
"tags": ["coffee"]
}
]
Retrieves all the existent Posts matching the given Title.
GET /posts?title={post_title}
Content-type: application/json
[
{
"id": "219970669169869319",
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
}
]
$ curl -XGET 'http:https://localhost:8080/posts?title=My%20cat%20and%20other%20marvels'
It replaces an existent Post for the given Id with given fields. All fields should be provided in the representation along the request. If optional fields are not provided they will be set as empty. If the Post cannot be found, a 404 - Not Found
response is returned.
PUT /posts/{post_id}
Content-type: application/json
{
"title": "My dog and other marvels"
}
$ curl -XPUT -H "Content-type: application/json" -d '{
"title": "My dog and other marvels"
}' 'http:https://localhost:8080/posts/219871526709625348'
Status: 200 - OK
Content-type: application/json
{
"id": "219871526709625348"
"title": "My dog and other marvels",
"tags": []
}
INFO: note that as the 'tags' field has not been provided in the request, it has been set as empty.
Deletes an existent Post for the given Id. If the Post cannot be found, a 404 - Not Found
response is returned.
DELETE /posts/{post_id}
$ curl -XDELETE 'http:https://localhost:8080/posts/219871526709625348'
Status: 200 - OK
Content-type: application/json
{
"id": "219871526709625348",
"title": "My cat and other marvels",
"tags": ["pet", "cute"]
}
The persistence layer has been modeled after Domain-Driven Design Repository pattern.
A word on the Repository pattern...
Unlike DAOs, which are designed following a data access orientation, Repositories are implemented following a collection orientation. This means that their interface will mimic the one of a collection of objects rather than exposing a set of CRUD operations. The focus is put this way on the domain as a model rather than on data and any CRUD operations that may be used behind the scenes to manage the actual persistence.
It creates a new a Post for the given Id with the provided data. If a Post already exists for the given Id, its data is replaced with the one supplied.
Select(
Value("data"),
If(
Exists(Ref(Class("posts"), Value("1520225686617873"))),
Replace(
Ref(Class("posts"), Value("1520225686617873")),
Obj("data", Obj("title", Value("My cat and other marvels")))
),
Create(
Ref(Class("posts"), Value("1520225686617873")),
Obj("data", Obj("title", Value("My cat and other marvels")))
)
)
)
It saves several Posts within a single transaction. It uses the Map
function to iterate over a collection of entities and apply the above save query to them.
Map(
Arr(Obj("data", Obj("title", Value("My cat and other marvels")))),
Lambda(
Value("nextEntity"),
//-- Save Query goes here
)
It looks up a Post by its Id and returns its data back.
Select(
Value("data"),
Get(Ref(Class("posts"), "1520225686617873"))
)
It looks up all Posts in the class and returns its data back. First, all Posts Ids are found using the class Index
together with the Paginate
function and then its data is looked up through the Get
function.
SelectAll(
Value("data"),
Map(
Paginate(Match(Index("all_posts"))),
Lambda(Value("nextRef"), Select(Value("data"), Get(Var("nextRef"))))
)
)
It looks up all Posts matching the given Title and returns its data. The search is done using a previsouly created Index
. First, all Posts Ids are found using the Index
together with the Paginate
function and then its data is looked up through the Get
function.
SelectAll(
Value("data"),
Map(
Paginate(Match(Index("posts_by_title"), Value("My cat and other marvels"))),
Lambda(Value("nextRef"), Select(Value("data"), Get(Var("nextRef"))))
)
)
It removes the Post for the given Id if any and returns its data.
Select(
Value("data"),
Delete(Ref(Class("posts"), "1520225686617873"))
)