How to Build a To-Do List Back End With Vapor 4 and Swift

Get started with server-side Swift

Christian Weinberger
Better Programming
Published in
8 min readJun 20, 2020

--

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!

A screenshot of todo-backend
Add your server here and run the tests to get feedback

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 type open 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.

Add custom working directory

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:

Enable automatic migration

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

Running the Vapor API template

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:

$ /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.

Note: the /todos endpoint is already provided for you in the vapor api-template project.

  • Run the tests:
Test results from Todo-Backend

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:

Line 5: Add CORSMiddleware

If you refresh the Todo-Backend tests page, the first two tests should now pass:

Test results from Todo-Backend

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:

Add a new field `completed` to 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:

Add a new field `completed` to our Todo migration

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:

6 passed, 10 failing — way to go 😅

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:

Our `TodoAPIModel`

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.

Update existing routes to use `TodoAPIModel` instead of `Todo`

Re-run the project and reload the Todo-Backend tests. You’ll see that we have more tests passing now.

A todo has an URL, but calling it does not return the TODO yet

Add endpoint to get a single todo

Go to TodoController and add a new route handler getSingle(req:).

TodoController.swift — get single todo route handler

Add this route and the named parameter todoID to the end of routes.swift:

routes.swift — add route with named parameter for `:todoID`

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.

Todo-Backend is able to fetch a single todo now 🎉

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:

Wow! Only 3 failing tests now!

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 and CreateTodo migration.
  • Add the new field order to our TodoAPIModel, CreateTodoRequestBody and PatchTodoRequestBody.
  • 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:

Todo.swift with `order` field

Update the migration in CreateTodo.swift as well:

Update TodoAPIModel, CreateTodoRequestBody and PatchTodoRequestBody:

Add `order` to `TodoAPIModel` and update the convenience initializer
Add `order` to `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.

All tests passing ✅

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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Christian Weinberger
Christian Weinberger

Written by Christian Weinberger

Tech Enthusiast. Nerddad x 2. VP of Engineering @ Monta (monta.com)

No responses yet

Write a response