feat: Ports all existing articles and images.

This commit is contained in:
2025-02-20 12:14:09 -05:00
parent e0fb6129ad
commit 67dc3540d6
151 changed files with 10413 additions and 1631 deletions

View File

@@ -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 vapors<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 vapors <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(&quot;api&quot;, &quot;todos&quot;)
</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, lets 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, lets 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">&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
@@ -200,15 +194,12 @@ contents to match the following.</p>
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>The important parts here are the <code>&lt;script&gt;</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>&lt;script&gt;</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 theres 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 theres 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
&lt;/table&gt;
</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 its 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 its 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, lets make sure the project builds.</p>
<pre><code class="language-bash">swift build
</code></pre>
<p>This may take a minute if its the first time building the project as it has to fetch the<br />
dependencies. If you experience problems here then make sure you dont have typos in your files.</p>
<p>This may take a minute if its the first time building the project as it has to fetch the dependencies. If you experience problems here then
make sure you dont 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 werent 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 werent 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>