You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<p>Learn how to build a controller component that can serve models as JSON objects through a RESTful API written in Swift.</p>
109
109
<section>
110
110
111
-
<h2id="crud-create-read-update-and-delete">CRUD ~ Create, Read, Update and Delete</h2><p>We should start by implementing the non-generic version of our code, so after we see the pattern we can turn it into a more generalized Swift code. If you start with the <ahref="https://github.com/vapor/api-template" target="_blank">API template</a> project there is a pretty good example for almost everything using a Todo model.</p><blockquote><p>NOTE: Start a new project using the <ahref="http://docs.vapor.codes/3.0/getting-started/toolbox/" target="_blank">toolbox</a>, just run <code>vapor new myProject</code></p></blockquote><p>Open the project by double clicking the <code>Package.swift</code> file, that’ll fire up Xcode (you should be on version 11.4 or later). If you open the <code>Sources/App/Controllers</code> folder you’ll find a sample controller file there called <code>TodoController.swift</code>. We’re going to work on this, but first…</p><blockquote><p>A controller is a collection of request handler functions around a specific model.</p></blockquote><h2id="http-basics-request---response">HTTP basics: Request -> Response</h2><p><ahref="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP</a> is a text transfer protocol that is widely used around the web. In the beginning it was only used to transfer HTML files, but nowadays you can use it to request almost anything. It’s mostly a stateless protocol, this means you request something, you get back a response and that’s it.</p><p>It’s like ordering a pizza from a place through phone. You need a number to call (URL), you pick up the phone, dial the place, the phone company initializes the connection between (you & the pizza place) the two participants (the network layer does the same thing when you request an URL from a server). The phone on the other side starts ringing. 📱</p><p>Someone picks up the phone. You both introduce yourselves, also exchange some basic info such as the delivery address (server checks HTTP headers & discovers what needs to be delivered to where). You tell the place what kind of pizza you’d like to have & you wait for it. The place cooks the pizza (the server gathers the necessary data for the response) & the pizza boy arrives with your order (the server sends back the actual response). 🍕</p><blockquote><p>Everything happens asynchronously, the place (server) can fulfill multiple requests. If there is only one person who is taking orders & cooking pizzas, sometimes the cooking process will be blocked by answering the phone. Anyways, using non-blocking i/o is important, that’s why Vapor uses Futures & Promises from <ahref="https://github.com/apple/swift-nio" target="_blank">SwiftNIO</a> under the hood.</p></blockquote><p>In our case the request is a URL with some extra headers (key, value pairs) and a request body object (encoded data). The response is usually made of a HTTP status code, optional headers and response body. If we are talking about a RESTful API, the encoding of the body is usually JSON.</p><p>All right then, now you know the basics it’s time to look at some Swift code.</p><h2id="contents-and-models-in-vapor">Contents and models in Vapor</h2><p>Defining a data structure in Swift is pretty easy, you just have to create a struct or a class. You can also convert them back and forth to JSON using the built-in <ahref="https://theswiftdev.com/how-to-parse-json-in-swift-using-codable-protocol/">Codable protocol</a>. Vapor has an extension around this called Content. If you conform the the protocol (no need to implement any new functions, the object just needs to be Codable) the system can decode these objects from requests and encode them as responses.</p><p>Models on the other hand represent rows from your database. The <ahref="https://theswiftdev.com/a-tutorial-for-beginners-about-the-fluent-postgresql-driver-in-vapor-4/">Fluent</a> ORM layer can take care of the low level abstractions, so you don’t have to mess around with SQL queries. This is a great thing to have, read my other article if you like to know more about Fluent. 💾</p><p>The problem starts when you have a model and it has different fields than the content. Imagine if this Todo model was a User model with a secret password field? Would you like to expose that to the public when you encode it as a response? Nope, I don’t think so. 🙉</p><p>I believe that in most of the Cases the Model and the Content should be separated. Taking this one step further, the content of the request (input) and the content of the response (output) is sometimes different. I’ll stop it now, let’s change our Todo model according to this.</p><pre><codeclass="language-swift">import Fluent
111
+
<h2id="crud-create-read-update-and-delete">CRUD ~ Create, Read, Update and Delete</h2><p>We should start by implementing the non-generic version of our code, so after we see the pattern we can turn it into a more generalized Swift code. If you start with the <ahref="https://github.com/vapor/api-template" target="_blank">API template</a> project there is a pretty good example for almost everything using a Todo model.</p><blockquoteclass="note"><p>NOTE: Start a new project using the <ahref="http://docs.vapor.codes/3.0/getting-started/toolbox/" target="_blank">toolbox</a>, just run <code>vapor new myProject</code></p></blockquote><p>Open the project by double clicking the <code>Package.swift</code> file, that’ll fire up Xcode (you should be on version 11.4 or later). If you open the <code>Sources/App/Controllers</code> folder you’ll find a sample controller file there called <code>TodoController.swift</code>. We’re going to work on this, but first…</p><blockquote><p>A controller is a collection of request handler functions around a specific model.</p></blockquote><h2id="http-basics-request---response">HTTP basics: Request -> Response</h2><p><ahref="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP</a> is a text transfer protocol that is widely used around the web. In the beginning it was only used to transfer HTML files, but nowadays you can use it to request almost anything. It’s mostly a stateless protocol, this means you request something, you get back a response and that’s it.</p><p>It’s like ordering a pizza from a place through phone. You need a number to call (URL), you pick up the phone, dial the place, the phone company initializes the connection between (you & the pizza place) the two participants (the network layer does the same thing when you request an URL from a server). The phone on the other side starts ringing. 📱</p><p>Someone picks up the phone. You both introduce yourselves, also exchange some basic info such as the delivery address (server checks HTTP headers & discovers what needs to be delivered to where). You tell the place what kind of pizza you’d like to have & you wait for it. The place cooks the pizza (the server gathers the necessary data for the response) & the pizza boy arrives with your order (the server sends back the actual response). 🍕</p><blockquote><p>Everything happens asynchronously, the place (server) can fulfill multiple requests. If there is only one person who is taking orders & cooking pizzas, sometimes the cooking process will be blocked by answering the phone. Anyways, using non-blocking i/o is important, that’s why Vapor uses Futures & Promises from <ahref="https://github.com/apple/swift-nio" target="_blank">SwiftNIO</a> under the hood.</p></blockquote><p>In our case the request is a URL with some extra headers (key, value pairs) and a request body object (encoded data). The response is usually made of a HTTP status code, optional headers and response body. If we are talking about a RESTful API, the encoding of the body is usually JSON.</p><p>All right then, now you know the basics it’s time to look at some Swift code.</p><h2id="contents-and-models-in-vapor">Contents and models in Vapor</h2><p>Defining a data structure in Swift is pretty easy, you just have to create a struct or a class. You can also convert them back and forth to JSON using the built-in <ahref="https://theswiftdev.com/how-to-parse-json-in-swift-using-codable-protocol/">Codable protocol</a>. Vapor has an extension around this called Content. If you conform the the protocol (no need to implement any new functions, the object just needs to be Codable) the system can decode these objects from requests and encode them as responses.</p><p>Models on the other hand represent rows from your database. The <ahref="https://theswiftdev.com/a-tutorial-for-beginners-about-the-fluent-postgresql-driver-in-vapor-4/">Fluent</a> ORM layer can take care of the low level abstractions, so you don’t have to mess around with SQL queries. This is a great thing to have, read my other article if you like to know more about Fluent. 💾</p><p>The problem starts when you have a model and it has different fields than the content. Imagine if this Todo model was a User model with a secret password field? Would you like to expose that to the public when you encode it as a response? Nope, I don’t think so. 🙉</p><p>I believe that in most of the Cases the Model and the Content should be separated. Taking this one step further, the content of the request (input) and the content of the response (output) is sometimes different. I’ll stop it now, let’s change our Todo model according to this.</p><pre><codeclass="language-swift">import Fluent
112
112
import Vapor
113
113
114
114
final class Todo: Model {
@@ -134,7 +134,7 @@ <h2 id="crud-create-read-update-and-delete">CRUD ~ Create, Read, Update and Dele
134
134
self.title = title
135
135
}
136
136
}
137
-
</code></pre><p>We expect to have a title when we insert a record (we can generate the id), but when we’re returning Todos we can expose the id property as well. Now back to the controller.</p><blockquote><p>WARN: Don’t forget to run Fluent migrations first: <code>swift run Run migrate</code></p></blockquote><h3id="create">Create</h3><p>The flow is pretty simple. Decode the Input type from the content of the request (it’s created from the HTTP body) and use it to construct a new Todo class. Next save the newly created item to the database using Fluent. Finally after the save operation is done (it returns nothing by default), map the future into a proper Output, so Vapor can encode this to JSON format.</p><pre><codeclass="language-swift">import Fluent
137
+
</code></pre><p>We expect to have a title when we insert a record (we can generate the id), but when we’re returning Todos we can expose the id property as well. Now back to the controller.</p><blockquoteclass="warning"><p>WARN: Don’t forget to run Fluent migrations first: <code>swift run Run migrate</code></p></blockquote><h3id="create">Create</h3><p>The flow is pretty simple. Decode the Input type from the content of the request (it’s created from the HTTP body) and use it to construct a new Todo class. Next save the newly created item to the database using Fluent. Finally after the save operation is done (it returns nothing by default), map the future into a proper Output, so Vapor can encode this to JSON format.</p><pre><codeclass="language-swift">import Fluent
138
138
import Vapor
139
139
140
140
struct TodoController {
@@ -378,7 +378,7 @@ <h2 id="crud-create-read-update-and-delete">CRUD ~ Create, Read, Update and Dele
378
378
.init(id: self.id!.uuidString, title: self.title)
379
379
}
380
380
}
381
-
</code></pre><blockquote><p>NOTE: If the input is the same as the output, you just need one (<code>Context</code>?) struct instead of two.</p></blockquote><p>This is what’s left off the controller (not much, haha):</p><pre><codeclass="language-swift">struct TodoController: ApiController {
381
+
</code></pre><blockquoteclass="note"><p>NOTE: If the input is the same as the output, you just need one (<code>Context</code>?) struct instead of two.</p></blockquote><p>This is what’s left off the controller (not much, haha):</p><pre><codeclass="language-swift">struct TodoController: ApiController {
382
382
typealias Model = Todo
383
383
}
384
384
</code></pre><p>The router object also shortened a bit:</p><pre><codeclass="language-swift">func routes(_ app: Application) throws {
0 commit comments