
Backend con Swift ¡Creamos una web con Vapor, Bootstrap y Heroku!
Crea tu propio backend con Swift, tan solo tienes que usar el Framework Vapor. Hoy aprendemos a crear nuestro backend completamente en Swift, realizamos peticiones HTTP a un servidor de 3ros, creamos leafs y añadimos Bootstrap para darle estilo, y finalmente deployamos en Heroku!
Tabla de contenido

Hoy en SwiftBeta vamos a:
- Crear una web usando el framework Vapor
- Backend completamente en Swift, donde accederemos a una API de 3ros para extraer información y poderla mostrar en nuestra web con HTML
- Bootstrap para dar estilo a nuestra web
- Finalmente, deployaremos nuestra web en Heroku (todo completamente gratis).
Va a ser muy sencillo, pero vas a entender conceptos que he ido aprendiendo a lo largo de las últimas dos semanas. Hace unos días creé esta nueva web de SwiftBeta en Heroku (https://swiftbeta.herokuapp.com pero ya no está disponible) una web donde cada X tiempo irán apareciendo preguntas sobre el entorno Apple, tanto de Swift, SwiftUI, etc. Es un QUIZ donde cualquier puede colaborar y añadir una pregunta para que aparezca en la web, tan solo debe añadirla en un JSON que tengo en un repositorio de Github. (explicar estructura de la API, JSON)
La web que vamos a crear va a usar datos de la API de Rick and Morty.

Homebrew, Instalar el gestor de paquetes para macOS
Lo primero de todo es instalar la Toolbox de Vapor en nuestro mac. Si no tienes instalado Homebrew recomiendo que lo instales (y si ya lo tienes instalado ves directamente a la siguiente sección), tan solo debes ir al siguiente link:

y una vez allí copiar y pegar la URL que aparece para instalarlo, esta URL debes pegarla en tu terminal:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Instalar Vapor
Para instalar la Toolbox de Vapor con Homebrew, lanza este comando en tu terminal:
brew install vapor
Creamos nuestro primer proyecto con Vapor
Una vez instalado vapor en nuestro macOS, vamos a crear nuestro primer proyecto. Para hacerlo, lanzamos el siguiente comando en la terminal:
vapor new TutorialSwiftBeta
Al ejecutarlo, nos aparecerán varios prompt el primero nos pregunta si queremos usar Fluent, indicamos que no, ya que no vamos a utilizar ninguna base de datos en este tutorial.
El siguiente prompt nos indica si vamos a usar Leaf, en este caso indicamos yes, ya que al hacerlo nos generará nuestro index (los leafs serían como los .html de tu web)

Al hacerlo, se crea nuestra estructura del proyecto de Vapor con todos los ficheros necesarios:

Abrimos el proyecto en Xcode
Ahora, sin salir de la terminal, vamos a entrar dentro de nuestro proyecto. En mi caso voy a ejecutar dos comandos, uno para entrar al proyecto y otro para abrirlo con Xcode:
cd TutorialSwiftBeta
y luego
open Package.swift
Al hacerlo, se abrirá Xcode. Hay que esperar un rato a que se instalen todas las dependencias del proyecto. Una vez hecho, veremos una estructura de nuestro proyecto como muestra la siguiente imagen:

Compilamos el proyecto recién creado
Una vez hemos creado el proyecto y se han instalado las dependencias correctamente, vamos a compilar a ver qué ocurre.
Al hacerlo, vemos por consola que aparece información. Esta información nos servirá para orientarnos de todo lo que ocurre en nuestro servidor. En nuestro caso aparece un mensaje:
[ NOTICE ] Server starting on http://127.0.0.1:8080
Ahora, podemos abrir Safari (o vuestro explorador preferido) y poner la siguiente dirección localhost:8080. Al hacerlo, vemos que nos aparece un error

Este error es debido a que no puede encontrar la carpeta con los recursos de nuestra Web App. Para solucionarlo, nos vamos a nuestro target, y le damos a editar

Al darle a editar, nos vamos a la sección de Run y allí dentro, en Options, buscamos Working Directory, tal y como te muestro en la siguiente imagen:

Aquí dentro, vamos a seleccionar la ruta de nuestro proyecto. Marcamos la opción de Use custom working directory y añadimos la ruta de nuestro proyecto (en mi caso he creado el proyecto en el escritorio)

Si ahora volvemos a compilar, quizás os aparece un mensaje como el siguiente, tan solo debéis darle a OK

y ahora sí, vuelves al explorador (Safari, Chrome, etc) y refrescas. Verás que ahora no te aparece el error, sino que aparece el siguiente mensaje:

Estructura del proyecto
Tenemos varios ficheros que se han creado al crear el proyecto, pero nos vamos a centrar en:
- Routes
- Leafs
- Controllers
Si vamos al fichero de routes.swift, vemos que ya hay dos rutas creadas. Esta rutas son las que colocamos al lado de la dirección de nuestra web, en este caso podemos probar:
- localhost:8080 en este caso estamos retornando un EventLoopFuture<View>. Fíjate que al index.leaf, le estamos pasando un diccionario. Esta key, si abrimos index.leaf, vemos que se usa para mostrar el title y el tag h1 de nuestro "HTML"
- localhost:8080/hello en este caso estamos retornando una simple String. No le pasamos esta información a ningún fichero con extensión leaf, directamente lo mostramos en nuestro navegador.
Creamos nuestra primera ruta
Lo que vamos hacer a continuación es borrar el contenido de la función routes, ya que vamos a crear un controlador que va a contener esta lógica. Una vez borrado, nos vamos a la carpeta de Controllers, y vamos a crear un fichero llamado WebController

Dentro de este fichero, vamos a crear la siguiente struct:
import Foundation
import Vapor
struct WebController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
}
}
Dentro del método boot, vamos a añadir las rutas que queremos que tenga nuestra app. En nuestro caso solo va a tener una ruta, y la vamos a llamar info
func boot(routes: RoutesBuilder) throws {
routes.get("info") { request in
// TODO:
}
}
Cuando un user quiera navegar a esta ruta, nosotros lo que haremos será ejecutar cierta lógica. Esta lógica será:
- Realizar una petición HTTP a un servidor,
- Recibiremos un JSON
- Lo mapearemos a un modelo de nuestro domino
- El modelo lo enviaremos a un leaf para que el user pueda visualizarlo como si fuera un html
Esto es exactamente lo que hago en swiftbeta.herokuapp.com, pero en lugar de ir a buscar los datos a uno de mis repositorios. Lo que vamos hacer es ir a buscarlos a otra API, por ejemplo vamos a probar de usar la de Rick and Morty
Pues vamos a rellenar el TODO: de nuestra nueva ruta info. Aquí es donde pasa toda la magia de nuestro backend. Para hacerlo, vamos a crear dos structs para recuperar la información que queremos del JSON, también vamos a crear una función privada que se encargará de ejecutarse cada vez que se llame a esta ruta
struct ResultsDataModel: Codable {
let results: [CharacterDataModel]
}
struct CharacterDataModel: Codable {
let id: Int
let name: String
let image: String
}
Nuestro WebController quedaría de la siguiente manera:
import Vapor
struct WebController: RouteCollection {
let uri = URI(string:"https://rickandmortyapi.com/api/character")
func boot(routes: RoutesBuilder) throws {
routes.get("info", use: getInfo)
}
private func getInfo(_ req: Request) async throws -> View {
let context: EventLoopFuture<ResultsDataModel> = req.client.get(uri).flatMapThrowing { response -> Data in
guard response.status == .ok else {
throw Abort(.notFound)
}
guard let buffer = response.body,
let data = String(buffer: buffer).data(using: .utf8) else {
throw Abort(.badRequest)
}
return data
}.map { data in
let jsonDecoder = JSONDecoder()
do {
let dataModel = try jsonDecoder.decode(ResultsDataModel.self, from: data)
return dataModel
} catch {
return ResultsDataModel(results: [])
}
}
let dataModel = try await context.get()
print(dataModel)
return try await req.view.render("index", dataModel)
}
}
Antes de compilar, debemos añadir estas rutas de alguna manera a nuestra app. Para hacerlo nos vamos al fichero routes.swift. Allí, instanciamos nuestro WebController y lo registramos en nuestra app:
import Vapor
func routes(_ app: Application) throws {
let webController = WebController()
try app.register(collection: webController)
}
Vamos a compilar, y vamos a ver qué ocurre en la consola de nuestro backend (hemos añadido un print para ver que recibimos y parseamos correctamente la petición HTTP).
Al compilar vemos que hemos recibido perfectamente nuestra petición HTTP de la API de Rick and Morty:
ResultsDataModel(results: [App.CharacterDataModel(id: 1, name: "Rick Sanchez", image: "https://rickandmortyapi.com/api/character/avatar/1.jpeg"), App.CharacterDataModel(id: 2, name: "Morty Smith", image: "https://rickandmortyapi.com/api/character/avatar/2.jpeg"),
// ETC
Pero si te fijas, no se está viendo absolutamente nada en nuestro index.leaf, es decir, nuestro explorador nos muestra una web en blanco. Lo que vamos hacer ahora es modificar nuestro index.leaf para que sepa como recoger el dataModel que le estamos pasando y lo sepa visualizar en una tabla.
Abrimos Visual Studio code
En este caso, para modificar el leaf, voy abrir Visual Studio Code. tengo instalada una extensión llamada vapor-leaf que me resalta el código en ficheros con extensión .leaf. Recomiendo que instales alguna de ellas (hay varias) de esta manera te facilitará el modificar este fichero.

Una vez tenemos nuestro index.leaf, vamos a añadir una tablar de HTML, y en los leaf podemos utilizar if, for,etc para poder aplicar lógica o bucles. En este caso vamos a añadir el siguiente código:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>#(title)</title>
</head>
<body>
<h1>#(title)</h1>
<table>
<tr>
<th>Image</th>
<th>ID</th>
<th>Name</th>
</tr>
#for(result in results):
<tr>
<td>#(result.image)</td>
<td>#(result.id)</td>
<td>#(result.name)</td>
</tr>
#endfor
</table>
</body>
</html>
si compilamos nuestra app y refrescamos el navegador, vemos la siguiente tabla:

En lugar de ver la URL de la imagen, vamos a ver que se vea la imagen. Para hacerlo es muy simple, modificamos el contenido del for
#for(result in results):
<tr>
<td><img src="#(result.image)" alt="#(result.name)"></td>
<td>#(result.id)</td>
<td>#(result.name)</td>
</tr>
#endfor
Si compilamos, vamos a ver el cambio que pega nuestra web:

Integración con Bootstrap
Lo último que vamos a ver es cómo añadir Bootstrap a nuestra web. De esta manera podremos sacar ventaja, y tener un estilo añadiendo muy poco código.
En el header añadimos el CSS:
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
Antes de cerrar el body añadimos:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
Vamos a añadir también un div que sea el container de la table, y vamos añadir varias clases para dar el aspecto que queremos a nuestra table:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>#(title)</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<h1>#(title)</h1>
<div class="container">
<table class="table table-responsive table-dark table-striped table-bordered">
<tr>
<th>Image</th>
<th>ID</th>
<th>Name</th>
</tr>
#for(result in results):
<tr>
<td><img src="#(result.image)" alt="#(result.name)"></td>
<td>#(result.id)</td>
<td>#(result.name)</td>
</tr>
#endfor
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
Una vez tenemos nuestra app creada, hay que publicarla en algún sitio. Para hacerlo, vamos a usar Heroku, ya verás como es muy simple lanzar a producción tu nueva app.
Deployar nuestra web de Vapor a producción con Heroku
Lo primero de todo que necesitas es tener una cuenta creada en heroku.com, solo tienes que rellenar el típico formulario de registro. Una vez creada nos vamos a nuestro terminal de macOS y en la raíz de nuestro proyecto ejecutamos la siguiente instrucción para instalar heroku en nuestra máquina (para ello vamos a utilizar Homebrew):
brew tap heroku/brew && brew install heroku
Desde la raíz de nuestro proyecto de Xcode jecutamos en nuestro terminal:
heroku login
aparecerá un prompt para que hagamos el login en un explorador


Hacemos login con la web que se acaba de abrir, y automáticamente ya tendremos una sesión iniciada. El mensaje de nuestra terminal cambiará a:

Ahora ya podemos ejecutar varios comandos para crear nuestra app en heroku y deployar el código. Por lo tanto, lo siguiente que vamos a ejecutar en nuestra terminal va a ser:
heroku create tutorialswiftbeta --region eu
Al hacerlo, se creará una aplicación en heroku (estará vacía)

Fíjate que ya tenemos una URL asignada para nuestra web, en nuestro caso es https://tutorialswiftbeta.herokuapp.com, si la abrimos vemos un mensaje por defecto de Heroku. Ahora, vamos al momento más esperado, vamos a deployar nuestro código!
Vamos a conectar nuestro repositorio local (si no lo tienes inicializado ejecuta git init en el terminal). El repositorio local (todo nuestro código) lo vamos a conectar con la app que acabamos de crear en Heroku, para ello ejecutamos el siguiente comando en nuestra terminal:
heroku git:remote -a tutorialswiftbeta

Y ahora, vamos a crear un fichero dentro de nuestro proyecto de Xcode. Nos vamos a Xcode, y creamos un Procfile, y dentro de este fichero solo vamos a añadir una línea:
web: Run serve --env production --hostname 0.0.0.0 --port $PORT
Para que quede más claro te muestro una captura del fichero con su contenido

Puedes encontrar más información del Procfile en este enlace de la documentación de Vapor https://docs.vapor.codes/3.0/deploy/heroku/#procfile
Ahora haz un comit de tus cambios
git add .
git commit -m "First Commit"
Una comiteado, vamos a instalar una última dependencia que vamos a necesitar justo ahora.
heroku buildpacks:set vapor/vapor
Y por fín, ejecuta en el terminal
git push heroku master

Cuando acabe verás algo parecido a:

Este proceso puede tardar unos minutos, pero cuando acabe te indicará que ya puedes acceder a la misma URL que aparecía en uno de los pasos anteriores.
tutorialswiftbeta.herokuapp.com
Conclusión
Hoy hemos aprendido a cómo crear nuestra primera web app usando el framework VAPOR. Con muy pocas líneas de código hemos sacado algo muy interesante, y con muy pocos retoques puedes crear apps muy potentes. También hemos usado Bootstrap para darle un aspecto más atractivo a nuestra web y finalmente hemos deployado nuestros cambios en Heroku.