--- tags: general, software, programming summary: Build an example application using Vapor and HTMX. --- # Vapor + HTMX ## Introduction This post is a quick example of creating a very basic todo web application using `Vapor` a swift web framework and `Htmx`, with no custom javascript required. [Vapor](https://docs.vapor.codes) [Htmx](https://htmx.org) ## Getting Started To get started you must install the vapor command-line tool that will generate our project. ```bash brew install vapor ``` Next, generate the project using the vapor command-line tool.  ```bash vapor new todo-htmx --fluent.db sqlite --leaf ``` The above command will generate a new project that uses an `SQLite` database along with vapor's `Leaf` templating engine. You can move into the project directory and browse around the files that are generated. ```bash cd todo-htmx ``` ## Update the Controller Open the `Sources/App/Controllers/TodoController.swift` file. This file handles the api routes for our `Todo` database model. Personally I like to prefix these routes with `api`. Update the first line in the `boot(routes: RoutesBuilder)` function to look like this. ```swift let todos = routes.grouped("api", "todos") ``` Everything else can stay the same. This changes these routes to be exposed at `http://localhost:8080/api/todos`, which will allow our routes that return html views to be able to be exposed at `http://localhost:8080/todos`. ## Update the Todo Model A todo is not very valuable without a way to tell if it needs to be completed or not. So, let's add a field to our database model (`Sources/App/Models/Todo.swift`). Update the file to include the following: ```swift import Fluent import struct Foundation.UUID /// Property wrappers interact poorly with `Sendable` checking, causing a warning for the `@ID` property /// It is recommended you write your model with sendability checking on and then suppress the warning /// afterwards with `@unchecked Sendable`. final class Todo: Model, @unchecked Sendable { static let schema = "todos" @ID(key: .id) var id: UUID? @Field(key: "title") var title: String @Field(key: "complete") var complete: Bool init() {} init(id: UUID? = nil, title: String, complete: Bool) { self.id = id self.title = title self.complete = complete } func toDTO() -> TodoDTO { .init( id: id, title: $title.value, complete: $complete.value ) } } ``` Since we added a field to our database model, we also need to update the migration file (`Sources/App/Migrations/CreateTodo.swift`). ```swift import Fluent struct CreateTodo: AsyncMigration { func prepare(on database: Database) async throws { try await database.schema("todos") .id() .field("title", .string, .required) .field("complete", .bool, .required) .create() } func revert(on database: Database) async throws { try await database.schema("todos").delete() } } ``` This just adds our new field to the database schema when we run the migrations, which we will do later on in the tutorial. ### Update the Data Transfer Object We also need to add the `complete` field to our data transfer object (`DTO`). This model is used as an intermediate between our database and the user. ```swift import Fluent import Vapor struct TodoDTO: Content { var id: UUID? var title: String? var complete: Bool? func toModel() -> Todo { let model = Todo() model.id = id model.complete = complete ?? false if let title = title { model.title = title } return model } } ``` ## Generate the View Templates Our index template was already generated at `Resources/Views/index.leaf`, open the file and edit the contents to match the following. > Note: You can learn more about the > [leaf templating engine here.](https://docs.vapor.codes/leaf/getting-started/) ```html