How to Build a To-Do List Back End With Vapor 4 and Swift
Get started with server-side Swift

Preamble
Todo-Backend is a great project that provides a simple web API spec for managing a to-do list. You can implement the API yourself and provide an endpoint to learn more about the server framework of your choice (in this article we’re going to use ServerSideSwift with Vapor 4).
Once you have it set up you can add the test target URL here, run tests and implement your API until you satisfy all tests. It’s a great learning opportunity!

In this tutorial you will learn:
- How to use
Todo-Backend
and satisfy its spec using TDD - How to make your local backend available using
ngrok
- How to configure
CORS
- How to write routes
- How to CRUD to-dos
- How to work with model migrations
Setting up the Project
Creating the Vapor project
There are just a few steps required to get a fully running Vapor backend on your machine.
Clone the Vapor api-template
(branch: 4) into a folder of your choice:
$ git clone https://github.com/vapor/api-template.git -b 4 todo-backend-vapor
Note: currently the Vapor 4 template is in a branch called 4
. If you read this article later it may already be merged into master. If so, just remove -b 4
from the above command.
Open the project in Xcode:
- Drag and drop the folder or
Package.swift
into the Xcode app icon
Or:
- Via terminal, change the directory to your new project (in my case:
todo-backend-vapor
) and typeopen Package.swift
.
Add a working directory in Xcode
Edit your Run
scheme and enable “Use custom working directory”. Set it to the root of your project. This is required when running in Xcode to ensure our SQLite database file is stored there.

Enable auto migrations
Now let’s add one more step to enable auto-migration. Vapor’s ORM Fluent
will try to migrate your models/schemes on startup:

Wait until all packages are resolved, select the Run
scheme and run your project:

You’ll notice that db.sqlite
has been created. Feel free to inspect it with a database client of your choice, such as TablePlus or SQLiteStudio.
Switch to your browser and open http://localhost:8080. You should see that it’s working!
Setting up ngrok
To make your local back end visible and available to todobackend.com, we’re going to use ngrok
.
One time setup actions:
- Go to https://ngrok.com/download
- Download the client, unzip and save it somewhere. e.g. in
/Applications
- Create a free account with ngrok
- Find your auth token here: https://dashboard.ngrok.com/get-started/setup
- Connect your account, in the terminal type:
$ /Applications/ngrok authtoken {your auth token}
Start ngrok
- From now on, whenever you want to create a tunnel to your local backend, you can just type:
$ /Applications/ngrok http 8080
This will make your local port 8080 reachable via the URL provided by ngrok
:

Note: in a free account the URL changes whenever you stop/start ngrok. But this shouldn’t bother us now.
Copy the HTTPS URL to your browser. You should see that it works again. Great, your back end is now reachable from outside!
Note: ngrok
provides a great web interface where you can track incoming requests. This is very useful for debugging — you can open it by navigating to http://127.0.0.01:4040 in your browser.
Connecting your backend to Todo-Backend
Next you’ll connect you backend to Todo-Backend. Make sure your backend in Xcode is still running.
- Go to https://todobackend.com/specs/
- Add your https URL and append
/todos
e.g. https://e03cfeab1b7d.ngrok.io/todos
Note: the /todos
endpoint is already provided for you in the vapor api-template
project.
- Run the tests:

Great! It was able to access our server but all tests are failing. Now it’s time to satisfy the spec.
Implementing Your Todo-Backend API
Fixing CORS
CORS is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.
If you want to learn more about CORS, checkout this article by Mozilla.
In Vapor you can enable CORS by adding the CORSMiddleware
. In routes.swift
update the middlewares and add CORS with the default configuration:
If you refresh the Todo-Backend tests page, the first two tests should now pass:

As you can see, we not only satisfied the CORS requirements but also the POST /todos
test. Vapor’s api-template has already implemented this. You can find the endpoint in routes.swift
and the implementation in TodoController.swift
.
Implementing “delete all”
Todo-Backend expects a delete all todos
endpoint under DELETE /todos
. To implement this, switch to TodoController.swift
and add a new route handler:
Now switch to routes.swift
and add the DELETE /todos
endpoint to the end:
Run your app and refresh the Todo-Backend Tests:

Great — we’re already passing five tests!
Fix the TODO model
As outlined in the test output, Todo-Backend expects our Todo
model to have a field completed
. Let’s update our Todo
model:
// 1.)
Add a new field with the name completed
of type Bool
.
// 2.)
Update the initializer with a default value of false
for completed
.
Now you have to update the scheme. Go to CreateTodo.swift
and add the completed
field:
Now delete the db.sqlite file and run your project again.
Note: in a production environment you want to avoid deleting the database. In this case, you can also modify your migrations. Take a look here for more details.
Once your project is running, reload the Todo-Backend tests. You’ll notice that more tests are failing now and errors are piling up in your console. For example the previously passing /POST todos
test is failing. This is due to our new completed
field, which we made required but is not provided by Todo-Backend when creating a new todo.
Let’s update our TodoController
to work with specific models for creating a Todo. Switch to TodoController.swift
, add a new CreateTodoRequestBody
struct, and update create(req:)
:
Note: if you are new to Vapor and want to understand its asynchronicity and when to use map
, flatMap
or flatMapThrowing
checkout the Vapor 4 async docs.
Re-run the project and the Todo-Backend tests. We have already six passing tests:

Add a custom Todo API model
Todo-Backend expects all returned Todo objects to contain a property url
with a link to their resource. In a RESTful API this should be /todos/{resource identifier}
, where {resource identifier}
is the id
of our Todo
.
As it doesn’t make sense to add a URL to the database (this would be adding redundant information and the URL might change based on environments) we’ll introduce a new API model for our Todo
entity. In the Models
folder create a new file TodoAPIModel.swift
:
You’re creating a new model with the required url
property and a convenience initializer to easily init our new model with a Todo
.
Now you have to update theTodoController
route handlers for getting all todos and creating a todo to return TodoAPIModel
instead of Todo
.
Re-run the project and reload the Todo-Backend tests. You’ll see that we have more tests passing now.

Add endpoint to get a single todo
Go to TodoController
and add a new route handler getSingle(req:)
.
Add this route and the named parameter todoID
to the end of routes.swift
:
Note: you can learn about routes and named parameters in my article Routing in Vapor 4.
Re-run the project and reload the Todo-Backend tests.

Updating a Todo
The next missing piece is a route that allows Todo-Backend to update a todo’s title
and change the value of completed
. For this, we have to implement a new route PATCH /todos/{todoID}
and we introduce a new PatchTodoRequestBody
with optional fields.
What are we doing here?
// 1.)
We check if we can find a todo with todoID
.
// 2.)
Decode the request body into PatchTodoRequestBody
.
// 3.)
Find the todo in our database
// 4.)
Update title
if provided
// 5.)
Update completed
if provided
// 6.)
Save changes to database
// 7.)
Return our TodoAPIModel
Now, we add the new route to routes.swift
:
We re-run the project and reload the Todo-Backend tests:

Allow ordering the todos
Todo-Backend wants to be able to order our todos. We have to perform several steps for this:
- Add a new field
order
to ourTodo
model andCreateTodo
migration. - Add the new field
order
to ourTodoAPIModel
,CreateTodoRequestBody
andPatchTodoRequestBody
. - Update the
PATCH todos/:todoID
endpoint to allow updating the order.
Updating the model and migration
Add a new optional order
field to Todo.swift
and update the initializer:
Update the migration in CreateTodo.swift
as well:
Update TodoAPIModel
, CreateTodoRequestBody
and PatchTodoRequestBody
:
Update the update todo route handler to allow changing the order of an existing todo:
Now delete the database db.sqlite
, re-run the project, and reload the Todo-Backend tests.

That’s it — you did it!
You can find the complete source code here: https://github.com/cweinberger/todo-backend-vapor4
I hope you enjoyed my tutorial! If you have any questions, feel free to use the comments section.