feat: Ports all existing articles and images.
This commit is contained in:
@@ -61,8 +61,8 @@
|
||||
</div>
|
||||
<img alt="banner" src="http://localhost:3000/articles/images/2025-01-05-vapor-htmx-todo-app.png"/>
|
||||
<h2>Introduction</h2>
|
||||
<p>This post is a quick example of creating a very basic todo web application using <code>Vapor</code> a swift web<br />
|
||||
framework and <code>Htmx</code>, with no custom javascript required.</p>
|
||||
<p>This post is a quick example of creating a very basic todo web application using <code>Vapor</code> a swift web framework and <code>Htmx</code>, with no custom
|
||||
javascript required.</p>
|
||||
<p><a href="https://docs.vapor.codes">Vapor</a></p>
|
||||
<p><a href="https://htmx.org">Htmx</a></p>
|
||||
<h2>Getting Started</h2>
|
||||
@@ -73,23 +73,21 @@ framework and <code>Htmx</code>, with no custom javascript required.</p>
|
||||
<p><img src="/articles/images/2025-01-05-vapor.gif" alt="" /></p>
|
||||
<pre><code class="language-bash">vapor new todo-htmx --fluent.db sqlite --leaf
|
||||
</code></pre>
|
||||
<p>The above command will generate a new project that uses an <code>SQLite</code> database along with vapor’s<br />
|
||||
<code>Leaf</code> templating engine. You can move into the project directory and browse around the files that<br />
|
||||
are generated.</p>
|
||||
<p>The above command will generate a new project that uses an <code>SQLite</code> database along with vapor’s <code>Leaf</code> templating engine. You can move into
|
||||
the project directory and browse around the files that are generated.</p>
|
||||
<pre><code class="language-bash">cd todo-htmx
|
||||
</code></pre>
|
||||
<h2>Update the Controller</h2>
|
||||
<p>Open the <code>Sources/App/Controllers/TodoController.swift</code> file. This file handles the api routes for<br />
|
||||
our <code>Todo</code> database model. Personally I like to prefix these routes with <code>api</code>.</p>
|
||||
<p>Open the <code>Sources/App/Controllers/TodoController.swift</code> file. This file handles the api routes for our <code>Todo</code> database model. Personally I
|
||||
like to prefix these routes with <code>api</code>.</p>
|
||||
<p>Update the first line in the <code>boot(routes: RoutesBuilder)</code> function to look like this.</p>
|
||||
<pre><code class="language-swift">let todos = routes.grouped("api", "todos")
|
||||
</code></pre>
|
||||
<p>Everything else can stay the same. This changes these routes to be exposed at<br />
|
||||
<code>http://localhost:8080/api/todos</code>, which will allow our routes that return html views to be able to<br />
|
||||
be exposed at <code>http://localhost:8080/todos</code>.</p>
|
||||
<p>Everything else can stay the same. This changes these routes to be exposed at <code>http://localhost:8080/api/todos</code>, which will allow our routes
|
||||
that return html views to be able to be exposed at <code>http://localhost:8080/todos</code>.</p>
|
||||
<h2>Update the Todo Model</h2>
|
||||
<p>A todo is not very valuable without a way to tell if it needs to be completed or not. So, let’s add<br />
|
||||
a field to our database model (<code>Sources/App/Models/Todo.swift</code>).</p>
|
||||
<p>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
|
||||
(<code>Sources/App/Models/Todo.swift</code>).</p>
|
||||
<p>Update the file to include the following:</p>
|
||||
<pre><code class="language-swift">import Fluent
|
||||
import struct Foundation.UUID
|
||||
@@ -126,8 +124,7 @@ final class Todo: Model, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Since we added a field to our database model, we also need to update the migration file<br />
|
||||
(<code>Sources/App/Migrations/CreateTodo.swift</code>).</p>
|
||||
<p>Since we added a field to our database model, we also need to update the migration file (<code>Sources/App/Migrations/CreateTodo.swift</code>).</p>
|
||||
<pre><code class="language-swift">import Fluent
|
||||
|
||||
struct CreateTodo: AsyncMigration {
|
||||
@@ -144,11 +141,10 @@ struct CreateTodo: AsyncMigration {
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>This just adds our new field to the database schema when we run the migrations, which we will do<br />
|
||||
later on in the tutorial.</p>
|
||||
<p>This just adds our new field to the database schema when we run the migrations, which we will do later on in the tutorial.</p>
|
||||
<h3>Update the Data Transfer Object</h3>
|
||||
<p>We also need to add the <code>complete</code> field to our data transfer object (<code>DTO</code>). This model is used as<br />
|
||||
an intermediate between our database and the user.</p>
|
||||
<p>We also need to add the <code>complete</code> field to our data transfer object (<code>DTO</code>). This model is used as an intermediate between our database and
|
||||
the user.</p>
|
||||
<pre><code class="language-swift">import Fluent
|
||||
import Vapor
|
||||
|
||||
@@ -170,11 +166,9 @@ struct TodoDTO: Content {
|
||||
}
|
||||
</code></pre>
|
||||
<h2>Generate the View Templates</h2>
|
||||
<p>Our index template was already generated at <code>Resources/Views/index.leaf</code>, open the file and edit the<br />
|
||||
contents to match the following.</p>
|
||||
<p>Our index template was already generated at <code>Resources/Views/index.leaf</code>, open the file and edit the contents to match the following.</p>
|
||||
<blockquote>
|
||||
<p>Note: You can learn more about the<br />
|
||||
<a href="https://docs.vapor.codes/leaf/getting-started/">leaf templating engine here.</a></p>
|
||||
<p>Note: You can learn more about the <a href="https://docs.vapor.codes/leaf/getting-started/">leaf templating engine here.</a></p>
|
||||
</blockquote>
|
||||
<pre><code class="language-html"><!doctype html>
|
||||
<html lang="en">
|
||||
@@ -200,15 +194,12 @@ contents to match the following.</p>
|
||||
</body>
|
||||
</html>
|
||||
</code></pre>
|
||||
<p>The important parts here are the <code><script></code> tag in the head element which will include <code>Htmx</code> in our<br />
|
||||
project.</p>
|
||||
<p>The important parts here are the <code><script></code> tag in the head element which will include <code>Htmx</code> in our project.</p>
|
||||
<p>The head element also contains a link to a custom <code>css</code> stylesheet that we will create shortly.</p>
|
||||
<p>We add a <code>form</code> element that will be used to generate a new todo item in the database. This is a<br />
|
||||
basic / standard html form, but we are using <code>Htmx</code> to post the form contents to the route<br />
|
||||
<code>POST http://localhost:8080/todos</code>, which we will create shortly.</p>
|
||||
<p>Then there’s the <code>table</code> element that will contain the contents of our todos. When the page is<br />
|
||||
loaded it will use <code>Htmx</code> to fetch the todos from <code>GET http://localhost:8080/todos</code> route, which we<br />
|
||||
will create shortly.</p>
|
||||
<p>We add a <code>form</code> element that will be used to generate a new todo item in the database. This is a basic / standard html form, but we are
|
||||
using <code>Htmx</code> to post the form contents to the route <code>POST http://localhost:8080/todos</code>, which we will create shortly.</p>
|
||||
<p>Then there’s the <code>table</code> element that will contain the contents of our todos. When the page is loaded it will use <code>Htmx</code> to fetch the todos
|
||||
from <code>GET http://localhost:8080/todos</code> route, which we will create shortly.</p>
|
||||
<h3>Todos Table Template</h3>
|
||||
<p>Create a new view template that will return our populated table of todos.</p>
|
||||
<pre><code class="language-bash">touch Resources/Views/todos.leaf
|
||||
@@ -249,14 +240,12 @@ will create shortly.</p>
|
||||
#endfor
|
||||
</table>
|
||||
</code></pre>
|
||||
<p>Here, we just create a table that is 3 columns wide from a list of todos that we will pass in to the<br />
|
||||
template. We use <code>Htmx</code> to handle updating a todo if a user clicks a checkbox to mark the todo as<br />
|
||||
<code>complete</code>, we also add a button in the last column of the table that we use <code>Htmx</code> to handle<br />
|
||||
deleting a todo from the database.</p>
|
||||
<p>Here, we just create a table that is 3 columns wide from a list of todos that we will pass in to the template. We use <code>Htmx</code> to handle
|
||||
updating a todo if a user clicks a checkbox to mark the todo as <code>complete</code>, we also add a button in the last column of the table that we use
|
||||
<code>Htmx</code> to handle deleting a todo from the database.</p>
|
||||
<h2>Controllers</h2>
|
||||
<p>The controllers handle the routes that our website exposes. The project template creates a<br />
|
||||
controller for us that handles <code>JSON</code> / <code>API</code> requests, but we do need to make a couple of changes<br />
|
||||
to the file (<code>Sources/App/Controllers/TodoController.swift</code>).</p>
|
||||
<p>The controllers handle the routes that our website exposes. The project template creates a controller for us that handles <code>JSON</code> / <code>API</code>
|
||||
requests, but we do need to make a couple of changes to the file (<code>Sources/App/Controllers/TodoController.swift</code>).</p>
|
||||
<pre><code class="language-swift">import Fluent
|
||||
import Vapor
|
||||
|
||||
@@ -308,16 +297,13 @@ struct TodoController: RouteCollection {
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>The primary changes here are to add the <code>update(req: Request)</code> function at the bottom, which handles<br />
|
||||
updating a todo that has already been created. This will be used when a user clicks on the checkbox<br />
|
||||
to mark a todo as complete or incomplete.</p>
|
||||
<p>We also change the route in the <code>boot(routes: RoutesBuilder)</code> method to make all these routes<br />
|
||||
accessible at <code>/api/todos</code> instead of the original <code>/todos</code> as we will use the <code>/todos</code> routes for<br />
|
||||
returning our views from our view controller.</p>
|
||||
<p>The primary changes here are to add the <code>update(req: Request)</code> function at the bottom, which handles updating a todo that has already been
|
||||
created. This will be used when a user clicks on the checkbox to mark a todo as complete or incomplete.</p>
|
||||
<p>We also change the route in the <code>boot(routes: RoutesBuilder)</code> method to make all these routes accessible at <code>/api/todos</code> instead of the
|
||||
original <code>/todos</code> as we will use the <code>/todos</code> routes for returning our views from our view controller.</p>
|
||||
<h3>Todo View Controller</h3>
|
||||
<p>Next we need to create our view controller, it is what will be used to handle routes that should<br />
|
||||
return <code>html</code> content for our website. This controller will actually use the api controller to do<br />
|
||||
the majority of it’s work.</p>
|
||||
<p>Next we need to create our view controller, it is what will be used to handle routes that should return <code>html</code> content for our website. This
|
||||
controller will actually use the api controller to do the majority of it’s work.</p>
|
||||
<p>The easiest thing is to make a copy of the current api controller:</p>
|
||||
<pre><code class="language-bash">cp Sources/App/Controllers/TodoController.swift Sources/App/Controllers/TodoViewController.swift
|
||||
</code></pre>
|
||||
@@ -365,9 +351,8 @@ struct TodoViewController: RouteCollection {
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here we use the api controller to do the heavy lifting of communicating with the database, then we<br />
|
||||
just always return / render the <code>todos.leaf</code> template that we created earlier, which will update our<br />
|
||||
web page with the list of todos retreived from the database.</p>
|
||||
<p>Here we use the api controller to do the heavy lifting of communicating with the database, then we just always return / render the
|
||||
<code>todos.leaf</code> template that we created earlier, which will update our web page with the list of todos retreived from the database.</p>
|
||||
<blockquote>
|
||||
<p>Note: There are better ways to handle this, however this is just a simple example.</p>
|
||||
</blockquote>
|
||||
@@ -389,29 +374,29 @@ func routes(_ app: Application) throws {
|
||||
try app.register(collection: TodoViewController())
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here, we just add the <code>TodoViewController</code> at the bottom so vapor will be able to handle those<br />
|
||||
routes and also update the title to be <code>Todos</code> (in the first <code>app.get</code> near the top).</p>
|
||||
<p>Here, we just add the <code>TodoViewController</code> at the bottom so vapor will be able to handle those routes and also update the title to be
|
||||
<code>Todos</code> (in the first <code>app.get</code> near the top).</p>
|
||||
<h2>Build and Run</h2>
|
||||
<p>At this point we should be able to build and run the application.</p>
|
||||
<p>First, let’s make sure the project builds.</p>
|
||||
<pre><code class="language-bash">swift build
|
||||
</code></pre>
|
||||
<p>This may take a minute if it’s the first time building the project as it has to fetch the<br />
|
||||
dependencies. If you experience problems here then make sure you don’t have typos in your files.</p>
|
||||
<p>This may take a minute if it’s the first time building the project as it has to fetch the dependencies. If you experience problems here then
|
||||
make sure you don’t have typos in your files.</p>
|
||||
<p>Next, we need to run the database migrations.</p>
|
||||
<pre><code class="language-bash">swift run App migrate
|
||||
</code></pre>
|
||||
<p>Finally, we can run the application.</p>
|
||||
<pre><code class="language-bash">swift run App
|
||||
</code></pre>
|
||||
<p>You should be able to open your browser and type in the url: <code>http://localhost:8080</code> to view the<br />
|
||||
application. You can experiment with adding a new todo using the form.</p>
|
||||
<p>You should be able to open your browser and type in the url: <code>http://localhost:8080</code> to view the application. You can experiment with adding
|
||||
a new todo using the form.</p>
|
||||
<blockquote>
|
||||
<p>Note: To stop the application use <code>Ctrl-c</code></p>
|
||||
</blockquote>
|
||||
<h2>Bonus Styles</h2>
|
||||
<p>Hopefully you weren’t blinded the first time you opened the application. You can add custom styles<br />
|
||||
by creating a <code>css</code> file (<code>Public/css/main.css</code>).</p>
|
||||
<p>Hopefully you weren’t blinded the first time you opened the application. You can add custom styles by creating a <code>css</code> file
|
||||
(<code>Public/css/main.css</code>).</p>
|
||||
<pre><code class="language-bash">mkdir Public/css
|
||||
touch Public/css/main.css
|
||||
</code></pre>
|
||||
@@ -444,8 +429,8 @@ td {
|
||||
margin-left: 20px;
|
||||
}
|
||||
</code></pre>
|
||||
<p>Currently vapor does not know to serve files from the <code>Public</code> directory, so we need to update the<br />
|
||||
<code>Sources/App/configure.swift</code> file, by uncommenting the line near the top.</p>
|
||||
<p>Currently vapor does not know to serve files from the <code>Public</code> directory, so we need to update the <code>Sources/App/configure.swift</code> file, by
|
||||
uncommenting the line near the top.</p>
|
||||
<pre><code class="language-swift">import Fluent
|
||||
import FluentSQLiteDriver
|
||||
import Leaf
|
||||
@@ -471,7 +456,7 @@ public func configure(_ app: Application) async throws {
|
||||
<pre><code class="language-bash">swift run App
|
||||
</code></pre>
|
||||
<h2>Conclusion</h2>
|
||||
<p>I hope you enjoyed this quick example of using <code>Htmx</code> with <code>Vapor</code>. You can view the source files at<br />
|
||||
<p>I hope you enjoyed this quick example of using <code>Htmx</code> with <code>Vapor</code>. You can view the source files at
|
||||
<a href="https://github.com/m-housh/todo-htmx">here</a>.</p>
|
||||
</article>
|
||||
<div class="border-t border-light mt-8 pt-8">
|
||||
@@ -507,7 +492,6 @@ Programming, Home-Performance, and Building Science
|
||||
</div>
|
||||
<p>
|
||||
<a href="/articles/2024/free-as-in-freedom/"><div>
|
||||
<img alt="banner" src="http://localhost:3000/articles/images/2024-04-09-free-as-in-freedom.png"/>
|
||||
Salute to open-source software engineers
|
||||
</div></a>
|
||||
</p>
|
||||
@@ -521,7 +505,6 @@ Programming, Home-Performance, and Building Science
|
||||
</div>
|
||||
<p>
|
||||
<a href="/articles/2024/pgp-encryption-introduction/"><div>
|
||||
<img alt="banner" src="http://localhost:3000/articles/images/2024-04-04-pgp-encryption-introduction.gif"/>
|
||||
In this article I introduce PGP and show a use case for me, which perhaps you can use as well.
|
||||
What is PGP
|
||||
PGP stands for Pretty Good Privacy, it was first developed in 1991 by Phil Zimmermann. PGP uses
|
||||
@@ -555,6 +538,12 @@ cryptographic privacy and authentication and is...
|
||||
|
|
||||
<a href="mailto:michael@mhoush.com" rel="nofollow">Email</a>
|
||||
</p>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/keep-markup/prism-keep-markup.min.js">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js">
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user