This project uses Vapor 4 framework to build a fully-featured web app comprising a web API consumed by a web Client.
The models are persisted into a PostgreSQL database hosted in a Docker container.
To be able to run this project, you need to have Docker installed in your system.
- Usage and Configuration
- Seed Data
- Models
- Routes and Headers
- API Documentation
- Docker Commands
- Running Unit Tests
- Heroku Deployment
Either clone the repo or download, unzip and save the project on your system.
Using the terminal, navigate to the directory where the project is located.
Key | Default Value | Description |
---|---|---|
DATABASE_HOST |
localhost |
Postgres hostname |
DATABASE_NAME |
vapor_database |
Postgres database |
DATABASE_USERNAME |
vapor_username |
Postgres username |
DATABASE_PASSWORD |
vapor_password |
Postgres password |
docker run --name postgres \
-e POSTGRES_DB=vapor_database \
-e POSTGRES_USER=vapor_username \
-e POSTGRES_PASSWORD=vapor_password \
-p 5432:5432 -d postgres
docker run --name redis -p 6379:6379 -d redis
Note
Replace the PostgreSQL image environment variables -e
with the ones you set up. If you didn’t set the environment variables, leave the default values and these will be used to run the containers.
The redis
container is used to set up the Redis database. This database is only used by the web Client to cache sessions and tokens. The Redis configuration sets the hostname from the REDIS_HOSTNAME
environment variable if it exists or uses the localhost
default value otherwise .
Run the following command :
open Package.swift
This creates and opens an Xcode project from the Swift package, using Xcode’s support for Swift Package Manager. It will automatically begin downloading Swift Package Manager dependencies. This can take some time the first time you open a project.
You must then tell Vapor where the API is running. To do this, set a custom working directory.
Option-Click the Run button in Xcode to open the scheme editor. On the Options tab, click to enable Use custom working directory
and select the directory where the Package.swift
file lives. For more guidance, check Vapor’s documentation.
Make sure you have the deployment target set to My Mac
on Xcode, then build and run the application.
You first need to have Swift installed in your machine. Check Vapor's documentation for instructions.
Once done, you will be able to run the following command :
swift run
That will build and run the project. The first time you run this it will take some time to fetch and resolve the dependencies.
On both environments, once running you should see the following in your console:
[ NOTICE ] Server starting on http://127.0.0.1:8080
http://127.0.0.1:8080 is the root url of the web Client. The root url for the web API is http://127.0.0.1:8080/api.
This project's database is already populated with an initial set of data. The json files containing this data are located under the folder /Resources/SeedData
.
This is the data used in the section API Documentation below to provide examples of CURL requests.
You can also test the consumption of the API through the web Client.
Here are two users already created which you can use to authenticate :
Username | Password |
---|---|
janedoe | password |
johndoe | password |
The database stores the following models :
- User
- Token
- Tag
- Article
- Comment
Each model has an id
property which is a unique identifier for the entries in the database.
Some models define a createdAt
property and an updatedAt
property which are automatically set by Fluent to reflect creation time and update time respectively.
Based on these models, the API provides the below response Models to the Client.
The User model is returned during the registration process of a new user to enable the authentication process.
The password
property is securely saved in the database using the Bcrypt
hashing algorithm. During the login process, the password provided in the request's Authorization header is verified with Bcrypt's verification mechanism.
The profilePicture
property of the model is optional and stores the file name of the user profile picture.
{
"id"
: UUID
"firstName"
: String
"lastName"
: String
"username"
: String
"password"
: String
"email"
: String
"profilePicture"
: String
"createdAt"
: Date
}
A public version of the API User model which does not include the password is returned to the Client when requested.
{
"id"
: UUID
"firstName"
: String
"lastName"
: String
"username"
: String
"email"
: String
"profilePicture"
: String
"createdAt"
: Date
}
A token is generated following each user login request. The token value
is to be added to the bearer authentication for CRUD requests.
The Token model also stores a user
property providing the unique identifier of the authenticated user.
{
"id"
: UUID
"value"
: String
"user"
: {
"id"
: UUID
}
}
A tag can be associated with one or many articles. An article can have one or many tags.
{
"id"
: UUID
"name"
: String
}
The picture
property of the model is optional and stores the file name of the article picture.
The Article model also stores a user
property providing the unique identifier of the article’s author.
{
"id"
: UUID
"title"
: String
"description"
: String
"picture"
: String
"user"
: {
"id"
: UUID
}
,
"createdAt"
: Date
"updatedAt"
: Date
}
Along with the description
property relating to the comment description itself, the Comment model also stores an article
property providing the unique identifier of the commented article as well as an author
property providing the unique identifier of the comment’s author.
{
"id"
: UUID
"description"
: String
"createdAt"
: Date
"article"
: {
"id"
: UUID
}
,
"author"
: {
"id"
: UUID
}
}
This Comment model provides full information about the commented article.
{
"id"
: UUID
"description"
: String
"createdAt"
: Date
"article"
: {
"id"
: UUID
"title"
: String
"description"
: String
"picture"
: String
"user"
: {
"id"
: UUID
}
,
"createdAt"
: Date
"updatedAt"
: Date
}
}
This Comment model provides full information about the comment’s author.
{
"id"
: UUID
"description"
: String
"createdAt"
: Date
"author"
: {
"id"
: UUID
"firstName"
: String
"lastName"
: String
"username"
: String
"email"
: String
"profilePicture"
: String
"createdAt"
: Date
}
}
Here is a recap of the different API routes and their related attributes.
For further details and specific examples, see API Documentation section below.
Method | Route | Authorization | Content-Type | Body | Response Content |
---|---|---|---|---|---|
GET | /api/users | [User.Public] | |||
GET | /api/users/{userId} | User.Public | |||
GET | /api/articles/{articleId}/user | User.Public | |||
POST | /api/users | application/ json |
firstName lastName username password email profilePicture |
User | |
POST | /api/users/login | Basic | Token | ||
PUT | /api/users/{userId} | Bearer | application/ json |
firstName lastName username password email profilePicture |
User.Public |
GET | /api/tags | [Tag] | |||
GET | /api/tags/{tagId} | Tag | |||
GET | /api/articles/{articleId}/tags | [Tag] | |||
POST | /api/articles/{articleId}/tags | Bearer | application/ x-www-form- urlencoded |
List of tag names | [Tag] |
GET | /api/articles | [Article] | |||
GET | /api/articles/{articleId} | Article | |||
GET | /api/users/{userId}/articles | [Article] | |||
GET | /api/tags/{tagId}/articles | [Article] | |||
GET | /api/articles/search?term= | [Article] | |||
POST | /api/articles | Bearer | application/ json |
title description picture |
Article |
PUT | /api/articles/{articleId} | Bearer | application/ json |
title description picture |
Article |
DELETE | /api/articles/{articleId} | Bearer | |||
GET | /api/users/{userId}/comments | [Comment WithArticle] |
|||
GET | /api/articles/{articleId}/comments | [Comment WithAuthor] |
|||
POST | /api/articles/{articleId}/comments | Bearer | application/ json |
comment |
[Comment] |
This route returns the list of all users.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [User.Public] |
CURL Example :
curl -X GET \
http://localhost:8080/api/users | jq
This route returns a specified user.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | User.Public |
CURL Example :
curl -X GET \
http://localhost:8080/api/users/1C36BF09-2E46-47BB-A98C-E29B44FA124D | jq
This route provides the author of a specified article.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | User.Public |
CURL Example :
curl -X GET \
http://localhost:8080/api/articles/31B9718F-B385-4107-A262-C66FB5DD0F66/user | jq
This routes allows to create a new user. When successfully completed, the created user is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | application/json | firstName : String (required)lastName : String (required)username : String (required)password : String (required)email : String (required)profilePicture : String (optional) |
User |
CURL Example :
curl -H "Content-Type: application/json" \
-d '{"firstName":"foo","lastName":"bar","username":"foobar","password":"password","email":"foo@bar.com"}' \
-X POST \
http://localhost:8080/api/users | jq
This route allows the user to authenticate himself. When successfully completed, the user is authenticated and a token is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Basic | Not applicable | Not applicable | Token |
CURL Example :
curl -u janedoe:password \
-X POST \
http://localhost:8080/api/users/login | jq
This route allows to update a specified user. It requires the user to be authenticated and to be the same user as the one specified. When successfully completed, the updated user is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Bearer | application/json | firstName : String (optional)lastName : String (optional)username : String (optional)password : String (optional)email : String (optional)profilePicture : String (optional) |
User.Public |
CURL Example :
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer vxd2uFskmIT5OwfdLbqU+Q==" \
-d '{"email":"jane@doe.com"}' \
-X PUT \
http://localhost:8080/api/users/1C36BF09-2E46-47BB-A98C-E29B44FA124D | jq
This route returns the list of all tags.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [Tag] |
CURL Example :
curl -X GET \
http://localhost:8080/api/tags | jq
This route returns a specified tag.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | Tag |
CURL Example :
curl -X GET \
http://localhost:8080/api/tags/F39FF292-C38B-40D2-B221-A5E58716E68A | jq
This route provides the tags attached to a specified article.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [Tag] |
CURL Example :
curl -X GET \
http://localhost:8080/api/articles/31B9718F-B385-4107-A262-C66FB5DD0F66/tags | jq
This route allows to attach new tags to a specified article or to update existing ones. It requires the user to be authenticated and to be the author of the article. When successfully completed, the updated tags list is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Bearer | application/x-www-form-urlencoded | List of Tags : [String] |
[Tag] |
CURL Example :
curl -H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Bearer vxd2uFskmIT5OwfdLbqU+Q==" \
-d "vapor,web,frontend" \
-X POST \
http://localhost:8080/api/articles/98F8DF40-D111-4E41-A946-33FB6663F59C/tags | jq
This route returns the list of all articles.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [Article] |
CURL Example :
curl -X GET \
http://localhost:8080/api/articles | jq
This route returns a specified article.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | Article |
CURL Example :
curl -X GET \
http://localhost:8080/api/articles/31B9718F-B385-4107-A262-C66FB5DD0F66 | jq
This route provides the list of articles written by a specified user.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [Article] |
CURL Example :
curl -X GET \
http://localhost:8080/api/users/1C36BF09-2E46-47BB-A98C-E29B44FA124D/articles | jq
This route provides the list of articles associated with a specified tag.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [Article] |
CURL Example :
curl -X GET \
http://localhost:8080/api/tags/F39FF292-C38B-40D2-B221-A5E58716E68A/articles | jq
This route provides the list of articles whose title or description contains the specified term.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [Article] |
CURL Example :
curl -X GET \
http://localhost:8080/api/articles/search?term=leaf | jq
This route allows a specified user to create a new article. It requires the user to be authenticated. When successfully completed, the created article is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Bearer | application/json | title : String (required)description : String (required)picture : String (optional) |
Article |
CURL Example :
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer vxd2uFskmIT5OwfdLbqU+Q==" \
-d '{"title":"This is a title.", "description":"This is a description."}' \
-X POST \
http://localhost:8080/api/articles | jq
This route allows to update an article. It requires the user to be authenticated and to be the author of the article. When successfully completed, the updated article is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Bearer | application/json | title : String (optional)description : String (optional)picture : String (optional) |
Article |
CURL Example :
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer vxd2uFskmIT5OwfdLbqU+Q==" \
-d '{"title":"Build backend applications for iOS apps, frontend websites and stand-alone server applications with Vapor"}' \
-X PUT \
http://localhost:8080/api/articles/31B9718F-B385-4107-A262-C66FB5DD0F66 | jq
This route allows to delete an article. It requires the user to be authenticated and to be the author of the article. When successfully completed, the http status code 204 (No Content)
is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Bearer | Not applicable | Not applicable | Http status code |
CURL Example :
curl -X DELETE \
-o /dev/null -s -w "%{http_code}\n" \
http://localhost:8080/api/articles/F50C1005-4825-495C-B1DE-78A717D87080 \
-H "Authorization: Bearer vxd2uFskmIT5OwfdLbqU+Q==" | jq
This route provides the list of comments written by a specified user. It also returns the articles associated with these comments.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [CommentWithArticle] |
CURL Example :
curl -X GET \
http://localhost:8080/api/users/1BC36B40-2199-4300-A38D-6BCDF1FF7E64/comments | jq
This route provides the list of comments related to a specified article. It also returns the authors of these comments.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
None | Not applicable | Not applicable | [CommentWithAuthor] |
CURL Example :
curl -X GET \
http://localhost:8080/api/articles/31B9718F-B385-4107-A262-C66FB5DD0F66/comments | jq
This route allows a user to post a new comment about a specified article. It requires the user to be authenticated. When successfully completed, the created comment is returned.
Authorization | Content-Type | Body | Response Content |
---|---|---|---|
Bearer | application/json | comment : String (required) |
Comment |
CURL Example :
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer vxd2uFskmIT5OwfdLbqU+Q==" \
-d '{"comment":"This is a comment."}' \
-X POST \
http://localhost:8080/api/articles/0CC0F31E-C79A-4ADC-8EB7-9C8191148339/comments | jq
docker rm -f postgres
docker rm -f redis
docker run --name postgres \
-e POSTGRES_DB=vapor_database \
-e POSTGRES_USER=vapor_username \
-e POSTGRES_PASSWORD=vapor_password \
-p 5432:5432 -d postgres
docker run --name redis -p 6379:6379 -d redis
Replace the PostgreSQL image environment variables -e
if you have set them up or leave the default values.
docker exec -it postgres psql -U vapor_username vapor_database
This will connect you to the running PostgreSQL database inside the container. From there, you can perform queries.
Note
You can remove the containers and rerun them to reset the database and restart with the initial database contained in the project.
This project contains unit tests that can be run with Xcode. It also includes specific testing.Dockerfile
and docker-compose-testing
files enabling testing in a Linux environment.
Key | Default Value | Description |
---|---|---|
DATABASE_PORT |
5433 |
Postgres hostname |
DATABASE_NAME |
vapor-test |
Postgres database |
DATABASE_USERNAME |
vapor_username |
Postgres username |
DATABASE_PASSWORD |
vapor_password |
Postgres password |
To run the tests on Xcode, set the required database in Docker :
docker run --name postgres-test \
-e POSTGRES_DB=vapor-test \
-e POSTGRES_USER=vapor_username \
-e POSTGRES_PASSWORD=vapor_password \
-p 5433:5432 -d postgres
You can then launch the tests with Command-U
, or Product → Test.
Run the tests in Linux with the following command :
docker-compose -f docker-compose-testing.yml build
docker-compose -f docker-compose-testing.yml up \
--abort-on-container-exit
The configure.swift
file contains the configuration for a Heroku PostgreSQL database.
Heroku uses the DATABASE_URL
environment variable that will be set during the deployment.
This project also contains the Procfile
required for the deployment.
Check Vapor’s documentation for instructions about Heroku deployment.