<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zentered</title>
    <description>The latest articles on DEV Community by Zentered (@zentered).</description>
    <link>https://dev.to/zentered</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5074%2F4e168ec0-a5ac-420c-82e5-15499dc5ef48.png</url>
      <title>DEV Community: Zentered</title>
      <link>https://dev.to/zentered</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zentered"/>
    <language>en</language>
    <item>
      <title>Time Series Data with TDEngine and GraphQL</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Mon, 27 Nov 2023 21:18:57 +0000</pubDate>
      <link>https://dev.to/zentered/time-series-data-with-tdengine-and-graphql-4dbl</link>
      <guid>https://dev.to/zentered/time-series-data-with-tdengine-and-graphql-4dbl</guid>
      <description>&lt;h2&gt;
  
  
  Motivation and Introduction
&lt;/h2&gt;

&lt;p&gt;As part of the software team at &lt;a href="https://nevados.solar" rel="noopener noreferrer"&gt;Nevados&lt;/a&gt; we are building an operations and monitoring platform for the Nevados All Terrain Tracker®. A solar tracker is a device that orients a solar panel toward the sun. Every solar tracker constantly sends status information and readings, such as the current angle, temperature, voltages, etc. to our platform and we need to store this information for analysis and visualization. If the tracker is configured to send data every 5 seconds, we have 17,280 data points per tracker per day, 518,400 data points per tracker per month. That sums up a lot of information. This kind of data is called "time-series data" and as for all complex problems in software, there are several solutions (Time Series Databases) for it. The most famous ones being InfluxDB and TimescaleDB. For our platform, we decided to work with &lt;a href="https://www.tdengine.com/" rel="noopener noreferrer"&gt;TDEngine&lt;/a&gt;, a relatively new product that is optimized for IoT applications and works with the SQL query language.&lt;/p&gt;

&lt;p&gt;There were several arguments for this decision: &lt;em&gt;TDEngine&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is open source&lt;/li&gt;
&lt;li&gt;is optimized for IoT applications&lt;/li&gt;
&lt;li&gt;uses SQL, which is a language we are familiar with&lt;/li&gt;
&lt;li&gt;is available as a managed service and we can focus on building our application&lt;/li&gt;
&lt;li&gt;is easy to run locally via Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we'll go through the setup of a TDEngine database and tables and how to craft a GraphQL schema that allows us to query the data from various clients and applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with TDEngine
&lt;/h2&gt;

&lt;p&gt;The easiest way to get started with TDEngine is to use their cloud service. Go to the &lt;a href="https://cloud.tdengine.com/login" rel="noopener noreferrer"&gt;TDEngine&lt;/a&gt; and create an account. They have a few public databases we can use, which is great to put together a demo or experiment with queries.&lt;/p&gt;

&lt;p&gt;If you want to run TDEngine locally, you can use the Docker image and &lt;a href="https://docs.influxdata.com/telegraf" rel="noopener noreferrer"&gt;Telegraf&lt;/a&gt; to retrieve data from various sources and send them to the database, such as system information, ping statistics etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tdengine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tdengine/tdengine:latest&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tdengine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tdengine&lt;/span&gt;

    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;6030:6030&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;6041:6041&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;6043-6049:6043-6049&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;6043-6049:6043-6049/udp&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;data:/var/lib/taos&lt;/span&gt;

  &lt;span class="na"&gt;telegraf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf:latest&lt;/span&gt;
    &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;tdengine&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./telegraf.conf:/etc/telegraf/telegraf.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://docs.influxdata.com/telegraf/v1.27/configuration/" rel="noopener noreferrer"&gt;official documentation for the Telegraf configuration&lt;/a&gt; and the &lt;a href="https://docs.tdengine.com/third-party/telegraf/#configuration-steps" rel="noopener noreferrer"&gt;TDEngine documentation on Telegraf&lt;/a&gt;. In short, this would look something like this to connect to an MQTT topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;agent&lt;/span&gt;]
  &lt;span class="n"&gt;interval&lt;/span&gt; = &lt;span class="s2"&gt;"5s"&lt;/span&gt;
  &lt;span class="n"&gt;round_interval&lt;/span&gt; = &lt;span class="n"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;omit_hostname&lt;/span&gt; = &lt;span class="n"&gt;true&lt;/span&gt;

[[&lt;span class="n"&gt;processors&lt;/span&gt;.&lt;span class="n"&gt;printer&lt;/span&gt;]]

[[&lt;span class="n"&gt;outputs&lt;/span&gt;.&lt;span class="n"&gt;http&lt;/span&gt;]]
  &lt;span class="n"&gt;url&lt;/span&gt; = &lt;span class="s2"&gt;"http://127.0.0.1:6041/influxdb/v1/write?db=telegraf"&lt;/span&gt;
  &lt;span class="n"&gt;method&lt;/span&gt; = &lt;span class="s2"&gt;"POST"&lt;/span&gt;
  &lt;span class="n"&gt;timeout&lt;/span&gt; = &lt;span class="s2"&gt;"5s"&lt;/span&gt;
  &lt;span class="n"&gt;username&lt;/span&gt; = &lt;span class="s2"&gt;"root"&lt;/span&gt;
  &lt;span class="n"&gt;password&lt;/span&gt; = &lt;span class="s2"&gt;"taosdata"&lt;/span&gt;
  &lt;span class="n"&gt;data_format&lt;/span&gt; = &lt;span class="s2"&gt;"influx"&lt;/span&gt;

[[&lt;span class="n"&gt;inputs&lt;/span&gt;.&lt;span class="n"&gt;mqtt_consumer&lt;/span&gt;]]
  &lt;span class="n"&gt;topics&lt;/span&gt; = [
    &lt;span class="s2"&gt;"devices/+/trackers"&lt;/span&gt;,
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of setting everything up locally and waiting for the database to fill with information, we'll use the public database for this article, which contains ship movements from the 5 major US ports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using TDEngine with public ship movement data
&lt;/h3&gt;

&lt;p&gt;By default, the tables in TDEngine have an implicit schema, which means the schema adapts to the data that is written to the database. This is great for bootstrapping, but eventually, we want to switch to an explicit schema to avoid issues with incoming data. One thing that takes a little time to get used to is their concept of &lt;a href="https://docs.tdengine.com/develop/model/#create-stable" rel="noopener noreferrer"&gt;Super Tables&lt;/a&gt; ("STable" for short). In TDEngine there are tags (keys) and columns (data). For each key combination, a "table" is created. All tables are grouped in the STable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mcku3u2z0854b0wtd4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mcku3u2z0854b0wtd4p.png" alt="TDEngine vessel tables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the &lt;code&gt;vessel&lt;/code&gt; database, they have one STable called &lt;code&gt;ais_data&lt;/code&gt; which contains a lot of tables. Usually, we do not want to query on a per-table basis, but always use the STable to get accumulated data from all tables.&lt;/p&gt;

&lt;p&gt;TDEngine has a function &lt;code&gt;DESCRIBE&lt;/code&gt; which allows us to inspect the schema of a table or STable. The &lt;code&gt;ais_data&lt;/code&gt; has the following schema:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbvml9nm1yyrlqreg80k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbvml9nm1yyrlqreg80k.png" alt="TDEngine table schema"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The STable has two keys and six data columns. The keys are the &lt;code&gt;mmsi&lt;/code&gt; and the &lt;code&gt;name&lt;/code&gt;. We can use regular SQL statements to query the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vessel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ais_data&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ts&lt;/span&gt;                        &lt;span class="n"&gt;name&lt;/span&gt;      &lt;span class="n"&gt;latitude&lt;/span&gt;   &lt;span class="n"&gt;longitude&lt;/span&gt;
&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="n"&gt;T22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;419&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;  &lt;span class="n"&gt;GERONIMO&lt;/span&gt;  &lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;921673&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;40928&lt;/span&gt;
&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="n"&gt;T22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;985&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;  &lt;span class="n"&gt;GERONIMO&lt;/span&gt;  &lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;921688&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;40926&lt;/span&gt;
&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="n"&gt;T22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;784&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;  &lt;span class="n"&gt;GERONIMO&lt;/span&gt;  &lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;92169&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;40926&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that time-series data is usually very large, so we should always limit the resultset. There are a few time-series specific functions that we can use, like &lt;code&gt;PARTITION BY&lt;/code&gt; which groups results by key and is useful to get the latest update individual keys. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;last_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vessel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ais_data&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ts&lt;/span&gt;                        &lt;span class="n"&gt;name&lt;/span&gt;      &lt;span class="n"&gt;latitude&lt;/span&gt;   &lt;span class="n"&gt;longitude&lt;/span&gt;
&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="n"&gt;T13&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;951&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;  &lt;span class="n"&gt;SAN&lt;/span&gt; &lt;span class="n"&gt;SABA&lt;/span&gt;  &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;375961&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;86894&lt;/span&gt;
&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="n"&gt;T18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;230&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;  &lt;span class="n"&gt;SELENA&lt;/span&gt;  &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;678585&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1954&lt;/span&gt;
&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="n"&gt;T17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;  &lt;span class="k"&gt;SOME&lt;/span&gt; &lt;span class="n"&gt;TUESDAY&lt;/span&gt;  &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;676563&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;230606&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flffde1wj8u8nxi18szk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flffde1wj8u8nxi18szk4.png" alt="TDEngine SQL output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recommend reading their &lt;a href="https://docs.tdengine.com/cloud/taos-sql/" rel="noopener noreferrer"&gt;SQL Documentation&lt;/a&gt; for more examples. Before we move on, head to "Programming", "Node.js" and retrieve your &lt;code&gt;TDENGINE_CLOUD_URL&lt;/code&gt; and &lt;code&gt;TDENGINE_CLOUD_TOKEN&lt;/code&gt; variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL with Nexus.js, Fastify and Mercurius
&lt;/h2&gt;

&lt;p&gt;GraphQL is pretty well known these days and there are lots of good articles about it. We chose the technology as we collect and process information from different sources and GraphQL allows us to transparently combine them into a single API.&lt;/p&gt;

&lt;p&gt;We'll use the amazing &lt;a href="http://fastify.io" rel="noopener noreferrer"&gt;Fastify&lt;/a&gt; framework (by now the default choice for Node.js applications) and the &lt;a href="https://mercurius.dev" rel="noopener noreferrer"&gt;Mercurius&lt;/a&gt; adapter. The teams of Mercurius and Fastify worked together for a seamless experience and it's a great choice GraphQL APIs with a focus on performance. &lt;a href="https://nexusjs.org" rel="noopener noreferrer"&gt;GraphQL Nexus&lt;/a&gt; is a tool to build/generate the schema and resolvers, so we do not have to write everything by hand.&lt;/p&gt;

&lt;p&gt;There's a bit of setup code etc. to be done, which I'll skip here. You can find a full example on &lt;a href="https://github.com/zentered/tdengine-graphql-example" rel="noopener noreferrer"&gt;GitHub - tdengine-graphql-example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I want to elaborate on two things in this article that are rather specific:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the TDEngine Query library&lt;/li&gt;
&lt;li&gt;the GraphQL schema with Nexus&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  TDEngine Query library
&lt;/h3&gt;

&lt;p&gt;TDEngine has a &lt;a href="https://www.npmjs.com/package/@tdengine/rest" rel="noopener noreferrer"&gt;Node.js library&lt;/a&gt; that allows us to query the database. This makes it easy to connect and send queries, unfortunately the responses are a little difficult to work with. So we wrote a little wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tdengine&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tdengine/rest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tdEngineToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tdEngineUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../config.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;parseFields&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql-parse-fields&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tdOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tdConnect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tdengine&lt;/span&gt;
&lt;span class="nx"&gt;tdOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tdEngineToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;tdOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tdEngineUrl&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TdEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tdConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tdOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;TdEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetchData()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getErrCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMeta&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`fetchData() error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getErrStr&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getErrStr&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columnName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;columnName&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/`/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;last_row(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a TDEngine object that can be passed into GraphQL context. We'll primarily be using the &lt;code&gt;fetchData&lt;/code&gt; function where we can pass in a SQL query and get the results back as an array of objects. TDEngine returns the metadata (columns), errors and data separately. We'll use the metadata to map the columns into a regular list of objects. A special case here is the &lt;code&gt;last_row&lt;/code&gt; function. The columns are returned as &lt;code&gt;last_row(ts)&lt;/code&gt;, &lt;code&gt;last_row(name)&lt;/code&gt; etc. and we want to remove the &lt;code&gt;last_row&lt;/code&gt; part so the attribute maps 1:1 to the GraphQL schema. This is done in the &lt;code&gt;columnName.replace&lt;/code&gt; part.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraphQL Schema
&lt;/h3&gt;

&lt;p&gt;Unfortunately there is no schema generator like &lt;a href="https://postgraphile.org" rel="noopener noreferrer"&gt;Postgraphile&lt;/a&gt; for TDEngine and we don't want to write and maintain a pure GraphQL schema, so we'll use Nexus.js to help us with that. We'll start with two basic types: &lt;code&gt;VesselMovement&lt;/code&gt; and &lt;code&gt;Timestamp&lt;/code&gt; (which is a scalar type). &lt;code&gt;Timestamp&lt;/code&gt; and &lt;code&gt;TDDate&lt;/code&gt; are two different types to display the date as a timestamp or as a date string. This is useful for the client application (and during development), as it can decide which format to use. &lt;code&gt;asNexusMethod&lt;/code&gt; allows us to use the type as a function in the &lt;code&gt;VesselMovement&lt;/code&gt; schema. We can resolve the &lt;code&gt;TDDate&lt;/code&gt; right here in the type definition to use the original &lt;code&gt;ts&lt;/code&gt; timestamp value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scalarType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nexus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scalarType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;asNexusMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TDEngine Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TDDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scalarType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TDDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;asNexusMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tdDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TDEngine Timestamp as Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VesselMovement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VesselMovement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tdDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mmsi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latitude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;longitude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;speed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nav_status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For time-series types, we use the &lt;code&gt;Movement&lt;/code&gt; or &lt;code&gt;Series&lt;/code&gt; suffix for a clear separation of relational and time-series types in the interface.&lt;/p&gt;

&lt;p&gt;Now we can define the Query. We'll start with a simple query to get the latest movements from TDEngine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objectType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nexus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GenericQueries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latestMovements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VesselMovement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tdEngine&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filterFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tdEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;`select last_row(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) from vessel.ais_data partition by mmsi;`&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmy6gqb55nxyokgo09s9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmy6gqb55nxyokgo09s9.png" alt="GraphiQL output of the query"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/graphql/graphiql" rel="noopener noreferrer"&gt;GraphiQL&lt;/a&gt; is a great tool to test the API and explore the schema, you can enable it by passing &lt;code&gt;graphiql.enabled = true&lt;/code&gt; in Mercurius. With the query, we can see the latest movements of vessels grouped by &lt;code&gt;mmsi&lt;/code&gt;. Let's go a little further though. One of the biggest advantages of GraphQL is that is is a transparent layer to the client or application. We can fetch data from multiple sources and combine them into the same schema.&lt;/p&gt;

&lt;p&gt;Unfortunately, I wasn't able to find an easy/free API with extensive vessel information. There is &lt;a href="https://developers.sinay.ai/docs/ports-and-vessels" rel="noopener noreferrer"&gt;Sinay&lt;/a&gt;, but they only provide the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;mmsi&lt;/code&gt; and &lt;code&gt;imo&lt;/code&gt; in their Vessel response (which we already have in TDEngine). For the sake of the example, we assume we do not have the &lt;code&gt;name&lt;/code&gt; in our database and we need to retrieve it from Sinay. With the &lt;code&gt;imo&lt;/code&gt; we could also query CO2 emissions for a vessel or another API could be used to retrieve an image, the flag or other information, all of which can be combined in the &lt;code&gt;Vessel&lt;/code&gt; type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Vessel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vessel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mmsi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;imo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VesselMovement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see here, we can include a list field &lt;code&gt;movements&lt;/code&gt; with the time-series data from TDEngine. We'll add another query to fetch the vessel information and the resolver allows us to combine the data from TDEngine and Sinay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vessel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vessel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;mmsi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tdEngine&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nf"&gt;getVesselInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mmsi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;tdEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`select * from vessel.ais_data where mmsi = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mmsi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' order by ts desc limit 10;`&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;movements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwswb70qi2mfibs5ozbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwswb70qi2mfibs5ozbf.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎉 and here we have a working GraphQL API returning rows from TDEngine for a specific vessel we requested. &lt;code&gt;getVesselInformation()&lt;/code&gt; is a simple wrapper to fetch data from Sinay. We'll add the TDEngine results into the &lt;code&gt;movements&lt;/code&gt; attribute and GraphQL will take care of the rest and map everything to the schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Note: SQL Injection
&lt;/h3&gt;

&lt;p&gt;As with any SQL database, we need to be careful with user input. In the example above we use the &lt;code&gt;mmsi&lt;/code&gt; input directly, which makes this query vulnerable to SQL injections. For the sake of the example, we'll ignore this for now, but in "real world" applications, we should always sanitize user input. There are several small libraries around to sanitize strings, in most cases we only rely on numbers (pagination, limit etc.) and enums (sort order), which GraphQL checks for us.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Dmitry Zaets for pointing this out!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizations
&lt;/h2&gt;

&lt;p&gt;There are a few things that go beyond the scope of this article, but I want to mention them briefly:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pothos as spiritual successor to Nexus.js
&lt;/h3&gt;

&lt;p&gt;When we started the project, Nexus.js was the best choice to generate our GraphQL schema. Although stable and &lt;em&gt;somewhat feature-complete&lt;/em&gt;, it lacks maintenance and updates. There is a plugin-based GraphQL schema builder called &lt;a href="https://pothos-graphql.dev" rel="noopener noreferrer"&gt;Pothos&lt;/a&gt; which is a bit more modern and actively maintained. If you're starting a new project, I probably recommend using Pothos instead of Nexus.js.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Mo Sattler for pointing this out!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Field Resolvers
&lt;/h3&gt;

&lt;p&gt;As you can see in the &lt;code&gt;Vessel&lt;/code&gt; resolver above, both data sources are immediately fetched and processed. This means if the query is only for the &lt;code&gt;name&lt;/code&gt;, we still fetch the &lt;code&gt;movements&lt;/code&gt; for the response. And if the query is for the &lt;code&gt;movements&lt;/code&gt; only, we still fetch the name from Sinay and potentially pay for the request.&lt;/p&gt;

&lt;p&gt;That's a GraphQL anti-pattern and we can improve the performance by using the field information to only fetch the data that is requested. Resolvers have the field information as the fourth argument, but they're pretty difficult to work with. Instead, we can use &lt;a href="https://www.npmjs.com/package/graphql-parse-fields" rel="noopener noreferrer"&gt;&lt;code&gt;graphql-parse-fields&lt;/code&gt;&lt;/a&gt; to get a simple object of the requested fields and adjust the resolver logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Query Optimizations
&lt;/h3&gt;

&lt;p&gt;In our example queries, we use &lt;code&gt;select *&lt;/code&gt; to fetch all columns from the database even if they're not needed. This is obviously pretty bad and we can use the same field parser to optimize the sql queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;filterFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invalidFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;__typename&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;parsedFields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsedFields&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;invalidFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;filteredFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function returns a comma-separated list of fields from the GraphQL info.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filterFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tdEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`select last_row(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) from vessel.ais_data partition by mmsi;`&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we request &lt;code&gt;ts&lt;/code&gt;, &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt;, the query would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;last_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vessel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ais_data&lt;/span&gt; &lt;span class="k"&gt;partition&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;mmsi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With only a few columns in this table this might not matter much, but with more tables and complex queries, this can make a huge difference in application performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time Series functions
&lt;/h3&gt;

&lt;p&gt;TDEngine has some time-series specific extensions that should be used to improve performance. For example, to retrieve the latest entry, a traditional SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vessel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ais_data&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Takes 653ms to execute, while the "TDEngine" query takes only 145ms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;last_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vessel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ais_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are configuration options for each table to optimize for last_row/first_row functions and other cache settings. I recommend reading the &lt;a href="https://docs.tdengine.com/cloud/" rel="noopener noreferrer"&gt;TDEngine documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The simple version: In this article, we've set up a TDEngine time-series database and defined a GraphQL schema to allow client applications to connect &amp;amp; query data.&lt;/p&gt;

&lt;p&gt;There's a lot more to it. We have a boilerplate project to combine complex time-series data with relational data in a transparent interface. At Nevados, we're using PostgreSQL as a primary database and retrieve time-series data the same way as in the &lt;code&gt;movement&lt;/code&gt; example above. This is a great way to combine data from multiple sources in a single API. Another benefit is that the data is only fetched when requested, which adds a lot of flexibility to the client application. Last but not least, the GraphQL Schema works as a documentation and contract, so we can easily tick the "API Documentation" box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you have any questions or comments&lt;/strong&gt;, please &lt;a href="https://bsky.app/profile/zentered.co" rel="noopener noreferrer"&gt;reach out on BlueSky&lt;/a&gt; or &lt;a href="https://github.com/zentered/feedback/discussions/" rel="noopener noreferrer"&gt;join the discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>node</category>
      <category>javascript</category>
      <category>timeseries</category>
    </item>
    <item>
      <title>GitHub (Preview) Deployments with Google Cloud Platform</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Tue, 30 May 2023 18:43:17 +0000</pubDate>
      <link>https://dev.to/zentered/github-preview-deployments-with-google-cloud-platform-1e8b</link>
      <guid>https://dev.to/zentered/github-preview-deployments-with-google-cloud-platform-1e8b</guid>
      <description>&lt;h2&gt;
  
  
  Motivation and Introduction
&lt;/h2&gt;

&lt;p&gt;Build previews aren't anything new or innovative. Heroku had them for years. If you're building on Google Cloud Platform however, things are a little more difficult. Google Cloud Build can be used to deploy to Cloud Functions, Cloud Run, Kubernetes, App Engine etc. etc. This is what makes previews slightly complicated, as Cloud Build doesn't know where you're deploying your application or service to. They have a GitHub App to connect the repositories and show the build status in GitHub, but that's it. No previews, comments or any of the "normal" stuff we're used to from products like Heroku, Vercel or Fly.&lt;/p&gt;

&lt;p&gt;There's an "easy" way to post a comment on a GitHub Pull Request from Cloud Build with a simple HTTP request. But why easy when you can use the &lt;a href="https://docs.github.com/en/rest/deployments/deployments?apiVersion=2022-11-28"&gt;GitHub Deployments API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this API, you can create Environments, have different variables per environment and show build &amp;amp; deployment status directly on a Pull Request. And once the deployment is done on your CD platform, you can add the URL to the deployment status. This is what we're going to do in this article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--loP8Uml3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/og6avuoujif6ej957a8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--loP8Uml3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/og6avuoujif6ej957a8r.png" alt="Image description" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, there's another way of doing this by using the Deployments API directly with HTTP requests. This can be tricky sometimes though and you'll need to define a Personal Access Token to authenticate with GitHub. Instead, we decided to put together a little Node.js service that uses GitHub Apps to generate an app token and interact with the Deployments API. This allows a little finer control over the deployment process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;This article is exclusively written for Google Cloud Platform and GitHub. Basic understanding of Google Cloud Platform, Google Cloud Build and IAM permissions is assumed. You should have a Google Cloud Platform project set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Deployer (Node.js &amp;amp; Docker)
&lt;/h2&gt;

&lt;p&gt;GitHub Deployer is a tiny Node.js service that authorized with a GitHub App and interacts with the GitHub Deployments API. Commands are predefined and some information needs to be built in to the Docker image, as otherwise the runtime configuration in Cloud Build would get too complex.&lt;/p&gt;

&lt;p&gt;The Docker image is not ready built; you'll need to build it yourself and push it to your registry. The best place is Google Artifact Registry where the images for your application are stored as well (Container Registry is deprecated):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If not done already, create an Artifact Registry repository&lt;/li&gt;
&lt;li&gt;Create a GitHub App and install it on your organization

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/developers/apps/creating-a-github-app"&gt;Create a GitHub App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Required permissions: 'pull_requests', 'deployments', 'metadata'&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy the App ID (from the App Settings screen), and the App Installation ID (after clicking "Configure" you'll find the installation ID in the URL. No idea where else that can be found)&lt;/li&gt;
&lt;li&gt;Create &amp;amp; download a private key and convert it to base64 (&lt;code&gt;base64 -i your.private-key.pem&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HkznYoAd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze846zhcaxa3gmj7159b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HkznYoAd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze846zhcaxa3gmj7159b.png" alt="Image description" width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Docker image locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;REF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test &lt;/span&gt;us-central1-docker.pkg.dev/[PROJECT]/docker/gh-deployer:latest create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Required environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;REPO_NAME&lt;/code&gt;: &lt;strong&gt;required&lt;/strong&gt; the name of the repository (e.g. &lt;code&gt;gh-deployer&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REF&lt;/code&gt;: &lt;strong&gt;required&lt;/strong&gt; the branch, tag or SHA to deploy (e.g. &lt;code&gt;main&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ENVIRONMENT&lt;/code&gt;: &lt;strong&gt;required&lt;/strong&gt; the environment to deploy to (e.g. &lt;code&gt;preview&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TRANSIENT_ENVIRONMENT&lt;/code&gt;: &lt;strong&gt;optional/false&lt;/strong&gt; if set to &lt;code&gt;true&lt;/code&gt;, the deployment will be deleted after a certain time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DESCRIPTION&lt;/code&gt;: &lt;strong&gt;optional&lt;/strong&gt; a description of the deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following commands are available as first argument:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create&lt;/code&gt; - create a new deployment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pending&lt;/code&gt; - the build is pending&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;in_progress&lt;/code&gt; - the build is in progress&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;queued&lt;/code&gt; - the build is queued&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;success&lt;/code&gt; - the deployment was successful&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;error&lt;/code&gt; - something went wrong&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failure&lt;/code&gt; - the deployment failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Cloud Build the options are currently a bit limited. There's no &lt;code&gt;pending&lt;/code&gt; state as Cloud Build needs to start in order to create the initial deployment. &lt;code&gt;queued&lt;/code&gt; doesn't make much sense either, so in our own setup, we're using the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;gh-deployer/create&lt;/code&gt; to create the transient deployment for a commit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pgh-deployer/ending&lt;/code&gt; directly after create&lt;/li&gt;
&lt;li&gt;Run kaniko build to build the Docker image for deployment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gh-deployer/in_progress&lt;/code&gt; after the build is done and before the image is deployed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gh-deployer/success&lt;/code&gt; after the image is deployed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Again, with Cloud Build we don't know where the deployment is going to be, so there are two ways to pass the deployment URL to the &lt;code&gt;success&lt;/code&gt; step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In a build step, write the URL into &lt;code&gt;/workspace/deployer_environment_url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pass a second argument to the Docker image after &lt;code&gt;success&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We use Cloud Run for our deployments, so here's the build step to retrieve the deployment URL for a given Pull Request number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/google.com/cloudsdktool/cloud-sdk&lt;/span&gt;
    &lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PR_NUMBER=$_PR_NUMBER&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;gcloud run services list --project [project] --filter preview-$PR_NUMBER&lt;/span&gt;
      &lt;span class="s"&gt;--format "value(status.address.url)" &amp;gt; /workspace/deployer_environment_url&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cloud Build Configuration / Terraform
&lt;/h2&gt;

&lt;h3&gt;
  
  
  cloudbuild.yaml
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/zentered/gh-deployer/tree/main/cloudbuild"&gt;cloudbuild.yaml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a complete exmaple using the &lt;code&gt;cloudbuild.yaml&lt;/code&gt; configuration file. We use Kaniko to build and Cloud Run as deplyoment target.&lt;/p&gt;

&lt;p&gt;If you work with Terraform, there's a Terraform file afailable as well: &lt;a href="https://github.com/zentered/gh-deployer/tree/main/terraform"&gt;preview.tf&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/[project]/docker/gh-deployer:latest'&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REPO_NAME=$REPO_NAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TRANSIENT_ENVIRONMENT=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DESCRIPTION=Deploying to Google Cloud Run&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ENVIRONMENT=preview&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REF=$REF_NAME&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/[project]/docker/gh-deployer:latest'&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REPO_NAME=$REPO_NAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ENVIRONMENT=preview&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REF=$REF_NAME&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pending&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/kaniko-project/executor:latest'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--destination=us-central1-docker.pkg.dev/[project]/docker/[name]:$SHORT_SHA'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--use-new-run'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/[project]/docker/gh-deployer:latest'&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REPO_NAME=$REPO_NAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ENVIRONMENT=preview&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REF=$REF_NAME&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;in_progress&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/google.com/cloudsdktool/cloud-sdk&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;preview-$_PR_NUMBER&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--image=us-central1-docker.pkg.dev/[project]/docker/[name]:$SHORT_SHA'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--region=us-central1'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--memory=512Mi'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--platform=managed'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--allow-unauthenticated'&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcloud&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/google.com/cloudsdktool/cloud-sdk&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PR_NUMBER=$_PR_NUMBER&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;gcloud run services list --project [project] --filter preview-$PR_NUMBER --format "value(status.address.url)" &amp;gt; /workspace/deployer_environment_url&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/[project]/docker/gh-deployer:latest'&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REPO_NAME=$REPO_NAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ENVIRONMENT=preview&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REF=$REF_NAME&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;By using the Deployments API, you can trigger GitHub Actions on &lt;code&gt;deployment_status&lt;/code&gt;. This makes it easy to run quality assurance checks, end-to-end tests etc. on each preview build. Here's an example for running &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; on each preview using the &lt;code&gt;environment_url&lt;/code&gt; that is passed in for the &lt;code&gt;success&lt;/code&gt; state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;preview-qa&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deployment_status&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;playwright-qa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.deployment_status.state == 'success' }}&lt;/span&gt;
    &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Playwright Browsers&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm exec playwright install --with-deps -y&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Playwright tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm exec playwright test&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.event.deployment_status.environment_url}}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report/&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optimizations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;the basic image &lt;code&gt;gcr.io/google.com/cloudsdktool/cloud-sdk&lt;/code&gt; is relatively large. You can &lt;a href="https://cloud.google.com/sdk/docs/downloads-docker#alpine-based_images"&gt;build your own small gcloud image with Cloud Run components&lt;/a&gt; to speed up the builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q-Mo14X6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qlazkw7sgmx388jlpe8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q-Mo14X6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qlazkw7sgmx388jlpe8o.png" alt="Image description" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comments on GitHub Pull Requests are easy at first, but when you have multiple deployments per Pull Request, failing builds etc. they don't offer a lot of flexibility and are hard to maintain / update. The GitHub Deployments API offers an elegant way to create and manage deployments from any third party CI/CD system. With the &lt;code&gt;GitHub Deployer&lt;/code&gt; we tried to streamline the interaction with the deployments API and take care of some side effects or pitfalls that are impossible to solve with just the HTTP API.&lt;/p&gt;

&lt;p&gt;Another benefit of using the GitHub Deployments API is that you can use the &lt;code&gt;deployment_status&lt;/code&gt; trigger for GitHub Actions and get the &lt;code&gt;environment_url&lt;/code&gt; directly with the event payload.&lt;/p&gt;

&lt;p&gt;You can find a little more detailled installation / build instructions and all the code on the &lt;a href="https://github.com/zentered/gh-deployer/tree/main#github-deployer"&gt;GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you have any questions or comments&lt;/strong&gt;, please &lt;a href="https://bsky.app/profile/zentered.co"&gt;reach out on BlueSky&lt;/a&gt; / &lt;a href="https://twitter.com/zenteredco"&gt;Twitter&lt;/a&gt; or &lt;a href="https://github.com/zentered/feedback/discussions/"&gt;join the discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>github</category>
      <category>deployment</category>
      <category>devops</category>
    </item>
    <item>
      <title>Fastify DX and SolidJS in the Real World</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Wed, 20 Jul 2022 08:56:33 +0000</pubDate>
      <link>https://dev.to/zentered/fastify-dx-and-solidjs-in-the-real-world-i1p</link>
      <guid>https://dev.to/zentered/fastify-dx-and-solidjs-in-the-real-world-i1p</guid>
      <description>&lt;h2&gt;Motivation and Introduction&lt;/h2&gt;

&lt;p&gt;SolidJS ranked #1 in the 2021 "State of JS Front-End Frameworks", so we wanted
to see what the fuzz is about and give it a proper chance with a side project.
We started with a simple Single Page Application (SPA) and a few components but
wanted to add data with GraphQL. For GraphQL we needed some sort of
authentication, to identify users. This quickly turned more complex day by day.
The biggest pitfalls and challenges were understanding Reactivity in SolidJS and
the use of context providers. Last but not least, we wanted to add some
server-side rendering capabilities, which lead to a three week rabbit hole of
reading SolidJS code, &lt;a href="https://github.com/fastify/fastify-vite"&gt;Fastify Vite&lt;/a&gt;
and lots of trial and error. Fortunately,
&lt;a href="https://github.com/galvez"&gt;Jonas Galvez&lt;/a&gt; was already working on
&lt;a href="https://github.com/fastify/fastify-dx"&gt;Fastify DX&lt;/a&gt; a new full stack framework
based on Fastify and Vite, where he added support for SolidJS as well. A nice
side effect was that we not only have server-side rendering, but also async
rendering, streaming web components/html streaming&lt;a href="https://www.section.io/html-streaming/"&gt;1&lt;/a&gt; available, which is pretty
awesome. As most of this is new and a lot of those technologies are barely
documented past "Hello World" or "Todo List", we decided to extract the most
important/difficult parts of our project into this "Real World Application with
SolidJS and Fastify DX".&lt;/p&gt;

&lt;p&gt;TLDR; You can skip ahead and browse through the code on GitHub:
&lt;a href="https://github.com/zentered/fastify-dx-solidjs-example"&gt;Fastify DX with SolidJS&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Overview of The Real World Application&lt;/h2&gt;

&lt;p&gt;Foreword: SolidJS looks a bit like React, but it really isn't. Things like
&lt;code&gt;.map()&lt;/code&gt; shouldn't be done, as it's not "reactive". Make sure you read up on
&lt;a href="https://www.solidjs.com/guides/reactivity"&gt;SolidJS Reactivity&lt;/a&gt; and the basic
concepts, otherwise you'll run into a lot of issues that are simple to avoid.&lt;/p&gt;

&lt;p&gt;Let's start with data. We live in amazing times and it's really easy and cheap
(or free) to get started with storing and working with data online. Take for
example a &lt;a href="https://planetscale.com"&gt;PlanetScale&lt;/a&gt; MySQL-compatible database,
&lt;a href="https://fastify.io"&gt;Fastify&lt;/a&gt; Node.js Server, &lt;a href="https://www.prisma.io"&gt;Prisma&lt;/a&gt;
database mapper and a &lt;a href="https://www.prisma.io/graphql"&gt;GraphQL&lt;/a&gt; connector like
&lt;a href="https://github.com/mercurius-js/mercurius"&gt;Mercurius&lt;/a&gt; and you have an entire
backend stack. For this example we assume you already have a backend or you want
to connect to a 3rd party database like the
&lt;a href="https://docs.github.com/en/graphql"&gt;GitHub GraphQL API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To retrieve data from your own or a 3rd party API, you most likely need access
tokens, this is where &lt;a href="https://auth0.com"&gt;Auth0&lt;/a&gt; comes in. You can either
retrieve JWT tokens for your own back-end and decrypt them on your API, or
retrieve access tokens for the social providers such as GitHub, Twitter, etc. to
interact with their API. In our example we decided to use the GitHub GraphQL API
to simply retrieve a list of popular repositories for the logged in user, and
let them browser to the repository detail page.&lt;/p&gt;

&lt;p&gt;To sum up the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Front-end: SolidJS&lt;/li&gt;
&lt;li&gt;Server-side rendering &amp;amp; streaming: Fastify DX&lt;/li&gt;
&lt;li&gt;Data layer: GraphQL&lt;/li&gt;
&lt;li&gt;Backend: GitHub GraphQL API&lt;/li&gt;
&lt;li&gt;Authentication: Auth0 with GitHub Social Connection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started with the authentication setup.&lt;/p&gt;

&lt;h2&gt;Getting started with Auth0&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MvibbD3H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/unauthorized-fallback.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MvibbD3H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/unauthorized-fallback.png" alt="screenshot of our login screen" width="643" height="473"&gt;&lt;/a&gt;Login Screen as fallback&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hint:&lt;/em&gt; There's a &lt;code&gt;env.example&lt;/code&gt; file in the repository which you can rename to
&lt;code&gt;.env&lt;/code&gt; and store the variables from the next few steps.&lt;/p&gt;

&lt;p&gt;Auth0 provides the &lt;a href="https://github.com/auth0/auth0-spa-js"&gt;auth0-spa-js&lt;/a&gt; package
which offers two ways to authenticate users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;login with popup&lt;/li&gt;
&lt;li&gt;login with redirect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usually the login with redirect is preferable, but there are some issues
retaining the state / session when coming back from the login, so we're
currently using "login with popup". The function opens "Auth0 Lock", a
pre-defined UI that has all the different authentication mechanisms that are
enabled for the application, and can be customized in the Auth0 settings. In
order to retrieve GitHub access tokens, we need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Auth0 Single Page Application to handle the login on our web app&lt;/li&gt;
&lt;li&gt;GitHub OAuth App to communicate with Auth0 securely and issue GraphQL API
tokens&lt;/li&gt;
&lt;li&gt;Auth0 Machine to Machine Application to retrieve access tokens for the GitHub
GraqhQL API through Auth0&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Auth0 Single Page Application&lt;/h3&gt;

&lt;p&gt;This is the primary Auth0 application we'll use. Create a new Single Page
Application in Auth0 and copy the client id and domain into &lt;code&gt;.env&lt;/code&gt;, (variables
starting with &lt;code&gt;VITE_AUTH0_&lt;/code&gt;). Make sure you add &lt;code&gt;http://localhost:8080&lt;/code&gt; to the
&lt;em&gt;Allowed Callback URL&lt;/em&gt;, &lt;em&gt;Allowed Web Origin&lt;/em&gt; and &lt;em&gt;Allowed Origin&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hdeYgIGx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/auth0-app-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hdeYgIGx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/auth0-app-settings.png" alt="screenshot showing the auth0 application client id and secret" width="880" height="474"&gt;&lt;/a&gt;Auth0 application client id and secret&lt;/p&gt;

&lt;h3&gt;GitHub OAuth App&lt;/h3&gt;

&lt;p&gt;Go into your &lt;a href="https://github.com/settings/developers"&gt;Developer Settings&lt;/a&gt; and
create a new OAuth App. Name, homepage etc. are not important, but the
&lt;code&gt;Authorization callback URL&lt;/code&gt; needs to point to your Auth0 Tenant. You can get
the domain in your Auth0 application settings:
&lt;code&gt;https://&amp;lt;auth0-app-DOMAIN&amp;gt;.auth0.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yt1-2CoC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/auth0-app-domain.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yt1-2CoC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/auth0-app-domain.png" alt="screenshot showing the auth0 domain" width="576" height="94"&gt;&lt;/a&gt;where to find the Auth0 domain&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KjjPi9lJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/github-oauth-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KjjPi9lJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/github-oauth-settings.png" alt="screenshot showing the github oauth app settings" width="880" height="1168"&gt;&lt;/a&gt;GitHub OAuth App settings&lt;/p&gt;

&lt;h3&gt;Auth0 Social Connections&lt;/h3&gt;

&lt;p&gt;Go to &lt;code&gt;Authentication -&amp;gt; Social&lt;/code&gt; and &lt;code&gt;Create Connection&lt;/code&gt;. Select GitHub. Pick a
name and copy the Client ID from your GitHub OAuth App. Go back to the GitHub
OAuth App settings and click &lt;code&gt;Create a new client secret&lt;/code&gt;. Copy that into the
Client Secret in the Auth0 connection details. For this app we only need
&lt;code&gt;public_repo&lt;/code&gt; as a permission. You should enable
&lt;code&gt;Sync user profile attribute at each login&lt;/code&gt; to retrieve the user details from
GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_c3x067g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/auth0-lock.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_c3x067g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/auth0-lock.png" alt="screenshot showing auth0 lock with github social login" width="512" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving, go back to your Single Page Application, to the tab &lt;code&gt;Connections&lt;/code&gt;
and add the GitHub Social Connection. You should disable the Database connection
to avoid issues later.&lt;/p&gt;

&lt;h3&gt;Auth0 Machine to Machine Application&lt;/h3&gt;

&lt;p&gt;We use a Machine to Machine Application to retrieve the Access Token for the
GitHub API through Auth0. This can't be done directly on the client.&lt;/p&gt;

&lt;p&gt;Go back to Applications, &lt;code&gt;Create Application&lt;/code&gt; and pick
&lt;code&gt;Machine to Machine Application&lt;/code&gt;. Enter a name, and copy the client id, secret
and domain into &lt;code&gt;.env&lt;/code&gt; (variables starting with &lt;code&gt;AUTH0_&lt;/code&gt;). Go to &lt;code&gt;APIs&lt;/code&gt; and
Authorize the application to the Auth0 Management API. The only permission
needed is &lt;code&gt;read:user_idp_tokens&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Auth Summary&lt;/h3&gt;

&lt;p&gt;The above steps look complicated, but they're actually just a few clicks and
values to copy&amp;amp;paste to have a complete and secure user authentication setup.
The UI and UX comes out of the box from Auth0, so there's very little we need to
do for a great signup experience.&lt;/p&gt;

&lt;p&gt;We created two "Auth0 Applications", the Single Page App to handle all things
login/user/etc. and the Machine to Machine App/Client to talk to the Management
API for the GitHub Access Tokens. We also created a GitHub OAuth App and
connected that with Auth0, so they can talk to each other and retrieve tokens.
This will allow us to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handle user login and signup through "Auth0 Lock"&lt;/li&gt;
&lt;li&gt;synchronize user data from GitHub to the Auth0 profile&lt;/li&gt;
&lt;li&gt;issue JWT tokens for our own web app and back-end&lt;/li&gt;
&lt;li&gt;retrieve GitHub access tokens for the GraphQL API on our web server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, to the code!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nYvkSqzs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/lets-code.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nYvkSqzs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/lets-code.jpeg" alt="Let's code hand dranw vector illustration in cartoon style. Happy programmer" width="591" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Fastify DX&lt;/h2&gt;

&lt;p&gt;Fastify DX is rad. You can use it with Vue, React, Svelte etc. and it runs on
our most beloved Node.js framework Fastify. Here's all we need to start a
server:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import Fastify from 'fastify'
import FastifyVite from 'fastify-vite'
import FastifyDXSolid from 'fastify-dx-solid'

const port = process.env.PORT ?? 5000
const logger = process.env.LOGGER ?? false

const server = Fastify({
  logger: logger
})

await server.register(FastifyVite, {
  root: import.meta.url,
  renderer: FastifyDXSolid
})

await server.vite.ready()
await server.listen({ host: '0.0.0.0', port: port })
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There's not much going on here; we're starting up Fastify and use the
FastifyVite plugin to build and render the Vite app on the fly. But since this
is a fully functional web server, we can also add our code to retrieve the
GitHub Access Tokens for the UI that we'll need later:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import { ManagementClient } from 'auth0'

server.post('/api/token', async (request, reply) =&amp;gt; {
  const userId = request.body.userId

  const management = new ManagementClient({
    domain: process.env.AUTH0_DOMAIN,
    clientId: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    scope: 'read:user_idp_tokens'
  })

  const user = await management.getUser({ id: userId })
  if (user &amp;amp;&amp;amp; user.identities) {
    return reply.send({ token: user.identities[0].access_token })
  }
  return reply.send({ ok: true })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In our &lt;code&gt;vite.config.js&lt;/code&gt; we'll add the SolidJS plugins and everything else needed
to render and build the SolidJS App. We'll also add the
&lt;a href="https://github.com/unocss/unocss"&gt;unocss&lt;/a&gt; engine. Some modules such as
&lt;code&gt;solid-app-router&lt;/code&gt; or &lt;code&gt;solid-headless&lt;/code&gt; require special handling for SSR, so they
have to be added into the &lt;code&gt;noExternal&lt;/code&gt; array in the config.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import { defineConfig } from 'vite'
import viteSolid from 'vite-plugin-solid'
import unocss from 'unocss/vite'
import viteSolidFastifyDX from 'fastify-dx-solid/plugin'
import { join, dirname } from 'path'
import { presetAttributify, presetUno, presetTypography } from 'unocss'
import { fileURLToPath } from 'url'

const path = fileURLToPath(import.meta.url)
const root = join(dirname(path), 'src') // 'client', 'src', ...

export default defineConfig({
  root: root,
  plugins: [
    viteSolid({ ssr: true }),
    viteSolidFastifyDX(),
    unocss({
      presets: [presetUno(), presetAttributify(), presetTypography()]
    })
  ],
  ssr: {
    noExternal: ['solid-app-router']
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To get your app up and running, copy the &lt;code&gt;context.js&lt;/code&gt;, &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;index.js&lt;/code&gt;
and &lt;code&gt;root.jsx&lt;/code&gt; from the &lt;code&gt;src/&lt;/code&gt; folder or directly from the
&lt;a href="https://github.com/fastify/fastify-dx/tree/main/starters/solid"&gt;Fastify DX Solid Starter Kit&lt;/a&gt;
(or the TypeScript version here:
&lt;a href="https://github.com/fastify/fastify-dx/tree/main/starters/solid-ts"&gt;Fastify DX Solid-TS&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;When running &lt;code&gt;pnpm dev&lt;/code&gt;, you should get a basic web server up and running
already, so let's move forward to the SolidJS app. Any errors, refer to the
example code or the Fastify DX starter kit, there are a few tiny details which
are easy to miss (so best just copy &amp;amp; paste).&lt;/p&gt;

&lt;h2&gt;SolidJS&lt;/h2&gt;

&lt;p&gt;At the time of putting together this application, this was the documented
example for &lt;code&gt;renderToStream&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// node
renderToStream(App).pipe(res)

// web stream
const { readable, writable } = new TransformStream()
renderToStream(App).pipeTo(writable)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's not much to go with, so this has been quite a challenge, mostly solved by
Jonas with Fastify DX. With SSR and streaming, there come a few new issues that
require special attention; the fact that some parts are pre-rendered on the
server and have a different context and state. Fastify DX provides &lt;code&gt;context.js&lt;/code&gt;,
a way to share state between client and server, which is super helpful. Using
the context providers properly and taking care of the reactivity chain is a
must.&lt;/p&gt;

&lt;p&gt;To make things slightly easier, we require auth for our entire app, so there are
no authenticated/unauthenticated routes to deal with. We also need the data
(GraphQL) context pretty much everywhere, so we'll wrap our app in those two
contexts:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;Auth&amp;gt;
  &amp;lt;GraphQL&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/GraphQL&amp;gt;
&amp;lt;/Auth&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The tricky part is that auth and GraphQL are both async, means we'll have to
wait for the auth client to be initialized and authenticated, and then
initialize and authenticate the GraphQL client. This was very painful to figure
out and lucky for you I've gone through that ordeal so you can just copy&amp;amp;paste
the solution:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;return (
  &amp;lt;Auth0
    domain={import.meta.env.VITE_AUTH0_DOMAIN}
    clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
  &amp;gt;
    &amp;lt;SiteRequiresAuth&amp;gt;
      &amp;lt;Router url={props.url}&amp;gt;
        &amp;lt;Routes&amp;gt;
          {
            // eslint-disable-next-line solid/prefer-for
            props.payload.routes.map((route) =&amp;gt; (
              &amp;lt;Route
                path={route.path}
                element={
                  &amp;lt;DXRoute
                    state={props.payload.serverRoute.state}
                    path={route.path}
                    payload={props.payload}
                    component={route.component}
                  /&amp;gt;
                }
              /&amp;gt;
            ))
          }
        &amp;lt;/Routes&amp;gt;
      &amp;lt;/Router&amp;gt;
    &amp;lt;/SiteRequiresAuth&amp;gt;
  &amp;lt;/Auth0&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Since we're on the front-end, &lt;code&gt;process.env&lt;/code&gt; doens't work; we use
&lt;code&gt;import.meta.env&lt;/code&gt; instead. Vite automatically exposes all variables that are
prefixed with &lt;code&gt;VITE_&lt;/code&gt;. The Auth0 context provides a client and [reactive
Signals](https://www.solidjs.com/guides/reactivity#introducing-primitives], but
those is only available &lt;em&gt;within&lt;/em&gt; the Auth0 context provider, so we'll need a
wrapper function &lt;code&gt;SiteRequiresAuth&lt;/code&gt; to get the initialized Auth0 context, fetch
the GitHub access token and initialize the GraphQL client. Only then do we show
any content of the site.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function SiteRequiresAuth(props) {
  const auth0 = useAuth0()
  const [accessToken] = createResource(() =&amp;gt; auth0.userId(), githubAuth)

  return (
    &amp;lt;Show
      when={auth0.isInitialized() &amp;amp;&amp;amp; auth0.isAuthenticated()}
      fallback={&amp;lt;Login auth0={auth0} /&amp;gt;}
    &amp;gt;
      &amp;lt;Show when={accessToken()}&amp;gt;
        &amp;lt;GraphQLProvider accessToken={accessToken}&amp;gt;
          {props.children}
        &amp;lt;/GraphQLProvider&amp;gt;
      &amp;lt;/Show&amp;gt;
    &amp;lt;/Show&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point I'd recommend to read up on
&lt;a href="https://www.solidjs.com/docs/latest/api#basic-reactivity"&gt;Basic Reactivity&lt;/a&gt; and
get familiar with the primitives.&lt;/p&gt;

&lt;p&gt;We use the control flow functions &lt;code&gt;&amp;lt;Show&amp;gt;&lt;/code&gt;, as they update when the signals (ie.
&lt;code&gt;isInitialized()&lt;/code&gt;) change or when the asynchronous resource &lt;code&gt;accessToken&lt;/code&gt; is
ready. As long as the Auth0 client is not initialized or authenticated, we'll
show the Login component. Once those are ready, we'll have to wait again for the
&lt;code&gt;accessToken&lt;/code&gt; and then finally show the rest of the site.&lt;/p&gt;

&lt;p&gt;The context providers are fairly useful now, as the variables are available
through the entire site. Anywhere, you can just &lt;code&gt;const auth = useAuth0()&lt;/code&gt; and
run &lt;code&gt;auth.user()&lt;/code&gt; to fetch the user details for example.&lt;/p&gt;

&lt;h3&gt;Fetching Data&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ONy-GmNB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/fetch.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ONy-GmNB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/fetch.jpeg" alt="Playful male lion carrying stick" width="662" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have an authenticated GraphQL client, we're able to run queries,
mutations etc. easily. Here's an example to retrieve the 10 "most starred"
repos:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import { gql } from '@urql/core'
import { useGraphQL } from '../contexts/GraphQL'

export default async function repos() {
  const gqlClient = useGraphQL()
  return gqlClient()
    ?.query(
      gql`
        query ($number_of_repos: Int!) {
          viewer {
            name
            repositories(
              orderBy: { field: STARGAZERS, direction: DESC }
              first: $number_of_repos
            ) {
              nodes {
                name
                stargazerCount
              }
            }
            login
          }
        }
      `,
      {
        number_of_repos: 10
      }
    )
    .toPromise()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We're retrieving the (authenticated) client context and run the query, simple.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import dataGitHubRepos from '../data/github-repos.data'
const [repos] = createResource(dataGitHubRepos)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;createResource&lt;/code&gt; is a
&lt;a href="https://www.solidjs.com/docs/latest/api#createresource"&gt;SolidJS reactive component&lt;/a&gt;
that processes async data and hydrates the components. This can be
async/sync/streamed/....&lt;/p&gt;

&lt;h3&gt;Pages &amp;amp; Routing&lt;/h3&gt;

&lt;p&gt;Fastify DX follows the same routing principles as &lt;a href="https://nextjs.com"&gt;Next.js&lt;/a&gt;
and &lt;a href="https://remix.run"&gt;Remix&lt;/a&gt;. The first page is &lt;code&gt;/pages/index.{js|ts}&lt;/code&gt; and
other pages can be linked to by using
&lt;a href="https://github.com/solidjs/solid-app-router"&gt;solid-app-router&lt;/a&gt;.
&lt;code&gt;&amp;lt;Link href="/dashboard"&amp;gt;Dashboard&amp;lt;/Link&amp;gt;&lt;/code&gt; would link to
&lt;code&gt;/pages/dashboard.{js|ts}&lt;/code&gt; and
&lt;code&gt;&amp;lt;Link href="/articles/awesome-solidjs"&amp;gt;SolidJS Article&amp;lt;/Link&amp;gt;&lt;/code&gt; would link to
&lt;code&gt;/pages/articles/[id].{js|ts}&lt;/code&gt;. SSR, Streaming etc. can be fine-tuned by
exporting variables in the page. Check out the examples for streaming, SSR, etc.
in the
&lt;a href="https://github.com/fastify/fastify-dx/tree/main/starters/solid/client/pages"&gt;fastify dx starter kit&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Components&lt;/h3&gt;

&lt;p&gt;Components work fairly similar to React, you can either &lt;code&gt;import&lt;/code&gt; or
&lt;a href="https://www.solidjs.com/docs/latest/api#lazy"&gt;&lt;code&gt;lazy()&lt;/code&gt; load them&lt;/a&gt;. Make sure
you use reactive primitives such as Signals, Memos, Resources etc. and pass them
to the component. SolidJS re-renders the component when the values change.
"Debugging" with &lt;code&gt;console.log&lt;/code&gt; is fairly useless, as most variables are only
updated when the reactive signals kick in. Sometimes just using
&lt;code&gt;&amp;lt;div&amp;gt;{val()}&amp;lt;/div&amp;gt;&lt;/code&gt; helps to see what's going on, but we're definitely looking
for better ways to debug code in SolidJS.&lt;/p&gt;

&lt;h2&gt;Useful Links, Caveats and TODOs&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/thetarnav/solid-devtools"&gt;SolidJS Developer Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.solidjs.com/guides/reactivity"&gt;SolidJS Guide to Reactivity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One issue at the moment is the hard refresh. The state is lost, the cookies gone
and the application shows our login screen. That's fairly annoying during
development.&lt;/p&gt;

&lt;p&gt;Another dent in the developer experience are unhandled client-side errors. They
break the reload/fresh cycle and the app becomes unresponsive. A full reload is
needed, causing the login screen to show again.&lt;/p&gt;

&lt;p&gt;Fastify DX also offers data function and a really amazing shared &lt;code&gt;context&lt;/code&gt;,
which we'd like to add some more examples for.&lt;/p&gt;

&lt;h2&gt;Questions, discussion and feedback&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dYRkXvAT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/thank-you.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dYRkXvAT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-07/thank-you.jpeg" alt="Cartoon Computer With the words Thank You!" width="699" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything looks simple after the fact, but this was a lot of work and effort to
piece together all the components and figure out how things fit together. We're
hoping to resolve the last few issues and have a boilerplate or starting point
for your next SolidJS application, that you can use and not go through the same
issues again.&lt;/p&gt;

&lt;p&gt;Special Thanks to &lt;a href="https://github.com/galvez"&gt;Jonas Galvez&lt;/a&gt; for the amazing work
at Fastify DX and the long, late night discussions we had.&lt;/p&gt;

&lt;p&gt;Thanks to the entire SolidJS team for this new exciting framework. Feels great
to work with and we're looking forward to build more apps with SolidJS.&lt;/p&gt;

&lt;p&gt;You can find the source code here:
&lt;a href="https://github.com/zentered/fastify-dx-solidjs-example"&gt;Fastify DX and SolidJS Example on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you have any questions or comments&lt;/strong&gt;, please
&lt;a href="https://twitter.com/zenteredco"&gt;reach out on Twitter&lt;/a&gt; or
&lt;a href="https://github.com/zentered/feedback/discussions/4"&gt;join the discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>fastifydx</category>
      <category>fastify</category>
      <category>solidjs</category>
      <category>auth0</category>
    </item>
    <item>
      <title>MDX 2 with Next.js and Embed Components (ESM version)</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Fri, 01 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/zentered/mdx-2-with-nextjs-and-embed-components-esm-version-3aa1</link>
      <guid>https://dev.to/zentered/mdx-2-with-nextjs-and-embed-components-esm-version-3aa1</guid>
      <description>&lt;h2&gt;Motivation and Introduction&lt;/h2&gt;

&lt;p&gt;We've crafted quite a few websites now with &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; and
we're really loving the experience and outcomes. Thanks to
&lt;a href="https://opstrace.com/"&gt;Opstrace&lt;/a&gt; we were able to Open Source a huge component
to render product documentation on your own Next.js page:
&lt;a href="https://github.com/opstrace/next-product-docs"&gt;Next Product Docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The entire Markdown ecosystem is evolving though and we're happy to welcome the
release of MDX 2 which brings a set of really cool new features, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📝 Improved syntax makes it easier to use markdown in JSX&lt;/li&gt;
&lt;li&gt;🧑‍💻 JavaScript expressions turn &lt;code&gt;{2 \* Math.PI}&lt;/code&gt; into &lt;code&gt;6.283185307179586&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🔌 New esbuild, Rollup, and Node.js integrations&lt;/li&gt;
&lt;li&gt;and much more. You can &lt;a href="https://mdxjs.com"&gt;read the announcement here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As it is in an ecosystem, some parts are moving faster or slower than others, so
moving to MDX 2 isn't without a few hoops to jump.&lt;/p&gt;

&lt;p&gt;We're going to integrate
&lt;a href="https://unifiedjs.com"&gt;unified&lt;/a&gt;/&lt;a href="https://github.com/remarkjs/remark"&gt;remark&lt;/a&gt;/&lt;a href="https://github.com/rehypejs/rehype"&gt;rehype&lt;/a&gt;
for improved Markdown handling and extensibility with useful features such as
GitHub Flavored Markdown, external link handling etc. and we're also integrating
embeds to show off your work on 3rd party platforms such as Twitter, Instagram
etc. rehype is a tool that transforms HTML with plugins, while remark transforms
Markdown.&lt;/p&gt;

&lt;p&gt;In this article we're using two different kind of embed mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/remark-embedder"&gt;remark-embedder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mdx-embed.com"&gt;mdx-embed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;remark-embedder&lt;/code&gt; is awesome because it converts simple links into embeds based
on the oEmbed format. Everything is done during build/render time, so the embed
code is rendered directly into the compiled &lt;code&gt;html&lt;/code&gt; without any client-side
JavaScript. You can find a
&lt;a href="https://oembed.com/providers.json"&gt;list of supported oEmbed providers here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mdx-embed&lt;/code&gt; takes a different approach and offers components that can be
imported in &lt;code&gt;mdx&lt;/code&gt;. They have a different set of components/embeds.&lt;/p&gt;

&lt;p&gt;The good news: you can use both approaches in the same file if you wish to.
Here's an example rendered with &lt;code&gt;remark-embedder&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;https://twitter.com/PatrickHeneise/status/1508503730295037954&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; it works with &lt;strong&gt;ESM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a3-fluSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-04/mdx-blog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a3-fluSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-04/mdx-blog.png" width="880" height="1421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;You can either &lt;a href="https://nextjs.org/docs"&gt;start from scratch&lt;/a&gt; or upgrade your
existing Next.js/MDX project. As a reference, we
&lt;a href="https://github.com/zentered/nextjs-mdx2"&gt;created a repository on GitHub&lt;/a&gt; with a
working example.&lt;/p&gt;

&lt;h3&gt;Setup&lt;/h3&gt;

&lt;p&gt;We're listing the required packages, &lt;em&gt;use your preferred package manager to
install those&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;The basics (MDX loader for Next.js):&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;    @mdx-js/loader @next/mdx
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;remark/rehype plugins&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;    rehype-external-links remark-gfm remark-frontmatter
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;List of remark plugins:
https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins&lt;/li&gt;
&lt;li&gt;List of rehype plugins:
https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;remark-embedder&lt;/h4&gt;

&lt;p&gt;If you want to include oEmbeds during build time:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    @remark-embedder/core @remark-embedder/transformer-oembed
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;mdx-embed&lt;/h4&gt;

&lt;p&gt;And last but not least "mdx-embed" for embedding components:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    mdx-embed
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Next.js Config&lt;/h4&gt;

&lt;p&gt;We're integrating mdx handling via webpack and the
&lt;a href="https://mdxjs.com/packages/loader/"&gt;@mdx-js/loader&lt;/a&gt;. You'll need to add the
&lt;code&gt;pageExtensions&lt;/code&gt; and webpack config. Feel free to add / remove remark/rehype
plugins here.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import remarkFrontmatter from 'remark-frontmatter'
import remarkGfm from 'remark-gfm'
import rehypeExternalLinks from 'rehype-external-links'

import fauxRemarkEmbedder from '@remark-embedder/core'
import fauxOembedTransformer from '@remark-embedder/transformer-oembed'
const remarkEmbedder = fauxRemarkEmbedder.default
const oembedTransformer = fauxOembedTransformer.default

const nextConfig = {
  reactStrictMode: true,
  experimental: { esmExternals: true },
  pageExtensions: ['md', 'mdx', 'jsx', 'js'],
  webpack: function (config) {
    config.module.rules.push({
      test: /\.mdx?$/,
      use: [
        {
          loader: '@mdx-js/loader',
          options: {
            rehypePlugins: [rehypeExternalLinks],
            remarkPlugins: [
              remarkGfm,
              remarkFrontmatter,
              [remarkEmbedder, { transformers: [oembedTransformer] }]
            ]
          }
        }
      ]
    })
    return config
  }
}

export default nextConfig
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;JSX Page&lt;/h4&gt;

&lt;p&gt;You can create a new page (ie &lt;code&gt;[post].jsx&lt;/code&gt;). We're able to use a dynamic import
to load the contents of the MDX file as a regular component. We're also
dynamically importing &lt;code&gt;mdx-embed&lt;/code&gt;, as the module is not ESM compatible yet.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import dynamic from 'next/dynamic'
import { getSlugs, getMeta } from 'utils/posts.js'
import { MDXProvider } from '@mdx-js/react'

export default function Post({ post, meta }) {
  // import mdx
  const Post = dynamic(import(`content/posts/${post}.mdx`))

  // dynamic import because not ESM compatible
  const embeds = dynamic(() =&amp;gt; import('mdx-embed'))
  const { CodePen, Gist } = embeds

  const components = {
    CodePen,
    Gist
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;main&amp;gt;
        &amp;lt;h1&amp;gt;{meta.title}&amp;lt;/h1&amp;gt;
        &amp;lt;article className="prose"&amp;gt;
          &amp;lt;MDXProvider components={components}&amp;gt;
            &amp;lt;Post /&amp;gt;
          &amp;lt;/MDXProvider&amp;gt;
        &amp;lt;/article&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;MDX Content&lt;/h4&gt;

&lt;p&gt;This is the file that's being rendered. It's a simple MDX file using regular
Markdown YML frontmatter, rather than an export. You can change that to your
liking. The components need to be imported again, otherwise there's an error,
you can find the full
&lt;a href="https://www.mdx-embed.com"&gt;list of supported components on the mdx-embed page&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;---
title: hello world
date: '2022-04-01'
preview: showing off twitter embeds
---

import { CodePen, Gist } from 'mdx-embed'

This is a demo post.

### Twitter Embed

https://twitter.com/PatrickHeneise/status/1508503730295037954

### The Gist ...

&amp;lt;Gist gistLink="PatrickHeneise/bbca1a8c4816f92aa3796db41a4a6203" /&amp;gt;

### And the Pen

&amp;lt;CodePen codePenId="PNaGbb" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In addition to those embed components, you can also add your own JSX components.
As an exmaple, we created a small &lt;code&gt;&amp;lt;Gallery&amp;gt;&lt;/code&gt; component taking an array of
images and displaying that in a grid:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IOqRdRKS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-04/mdx-gallery.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IOqRdRKS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-04/mdx-gallery.png" width="880" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Markdown for this is very simple:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;### Component

&amp;lt;Gallery images={['cyprus1.jpeg', 'cyprus2.jpeg', 'cyprus3.jpeg']} caption="This
is a caption" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This opens up a whole new world of possibilities when it comes to Markdown
content.&lt;/p&gt;

&lt;h2&gt;Summary &amp;amp; Feedback&lt;/h2&gt;

&lt;p&gt;In this article we explored the basics of and upgrade to MDX2. We also learned
how to add rehype/remark plugins to add features to our Markdown content during
render/build time and how to use the mdx-embeds to create an easy and rich
experience with 3rd party platforms. You can find the
&lt;a href="https://github.com/zentered/nextjs-with-mdx2"&gt;minimal example blog on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We hope you enjoyed this article and it'll help you on your journey with Next.js
and MDX2. If you have any feedback, comments, ideas about this article, please
&lt;a href="https://github.com/zentered/feedback/discussions/2"&gt;share them here&lt;/a&gt; or let us
know on &lt;a href="https://twitter.com/zenteredco"&gt;Twitter&lt;/a&gt;. We'd love to hear from you.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>mdx</category>
      <category>mdx2</category>
    </item>
    <item>
      <title>Issue Ops Meet Event Management</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Fri, 04 Mar 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/zentered/issue-ops-meet-event-management-5fl</link>
      <guid>https://dev.to/zentered/issue-ops-meet-event-management-5fl</guid>
      <description>&lt;h2&gt;Motivation&lt;/h2&gt;

&lt;p&gt;Events (meetups, conferences, talks, ...) are a crucial platform for people to
share information, collaborate, and grow. They help to make friends, get
condensed and focused information on a specific topic and learn new things. I
owe my entire career to events and the people I met along the way.&lt;/p&gt;

&lt;p&gt;In 2011 I had the opportunity to visit my first developer meetup
&lt;a href="https://berlinjs.org/"&gt;BerlinJS&lt;/a&gt;. I was new in the JavaScript world and really
loved the inclusivity and friendliness in that community. Right after going back
to Barcelona I founded &lt;a href="https://barcelonajs.com"&gt;BarcelonaJS&lt;/a&gt;. Since then a lot
has happened and I have been involved in many events and meetups, always trying
to find new ways to make the community better, more inclusive.&lt;/p&gt;

&lt;p&gt;Unfortunately the tools and infrastructure for event management have been very
centralized and restrictive. On meetup there is the "organizer" who is paying
for the group, and they're the one responsive on what's going on. While
traveling, I've seen groups with thousands of members but not a single event
(neither in person nor virtual), with some people trying desperately through the
comments or discussions to get people together.&lt;/p&gt;

&lt;p&gt;We've been looking for ways to make the event management more accessible and
decentralize the process. Making it easier for individual organizers by allowing
more people to collaborate in a community.&lt;/p&gt;

&lt;h2&gt;The beginning of GitEvents&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--02dwoXgD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-03/gitevents.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--02dwoXgD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/articles/2022-03/gitevents.svg" alt="GitEvents Logo" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gitevents/core/commit/d61dd9eb4ee715643fc21b7f6a1f9b0c7d1ef300"&gt;GitEvents was born in December 2014&lt;/a&gt;
as a joint project between BarcelonaJS, Node.js Barcelona and the London Node
User Group (LNUG). As developers, we're collaborating on code every day on
GitHub, so we thought it would be the perfect platform to also manage events
from there. Looking back, the tooling around it was terrible, but the best at
the time: a node.js server were GitHub Webhooks were sent to. A GitHub Issue
represented a talk for an event, a "Milestone" represented the upcoming events,
and "talks" were added to "events" by assigning the issues to milestones and
labeling them. We then created json files for each event with semantic
schema.org data, which we then used to generate a website.&lt;/p&gt;

&lt;h2&gt;GitEvents Resurrections&lt;/h2&gt;

&lt;p&gt;Fast forward to December 2021, we're back with a new version of GitEvents, as
nothing has improved (or got worse) in the event management space. The idea is
still the same: &lt;strong&gt;Issue-based event management&lt;/strong&gt;. We've simplified the
architecture; no server is needed any more and no complicated webhook
configuration. Instead we're using GitHub Actions to process issues and events
that happen on the repository. When the GitEvents Action is installed in a repo,
it can set up the required labels and issue templates with an easy setup script.
GitEvents runs with the permissions from a GitHub App, so no bot account is
needed, and it's clearly visible what it does when it labels or comments on
issues. One of the main features, inspired by
&lt;a href="http://github.com/steffen"&gt;Steffen&lt;/a&gt;, is the "auto invite" of everyone who
interacts with the organization. And since it's based on Open Source
Repositories on GitHub, everyone can add talks, event proposals etc. and a team
of organizers can moderate.&lt;/p&gt;

&lt;p&gt;We've started using GitEvents on the meetups we currently co-organize, such as
&lt;a href="https://cyprusjs.org"&gt;CyprusJS&lt;/a&gt; and the
&lt;a href="https://cdc.cy"&gt;Cyprus Developer Community&lt;/a&gt;. We'd love to hear from you if you
have any feedback or ideas for improvements. And we're happy to help you set up
GitEvents for your own meetup.&lt;/p&gt;

&lt;h3&gt;GitEvents Features&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Auto-invite everyone who interacts with the GitHub organization, be it a
comment, issue or discussion. This helps to create an inclusive space and
makes everyone feel welcome&lt;/li&gt;
&lt;li&gt;creation of the required labels and templates to run a meetup, inspired by
popular meetups like BerlinJS, BarcelonaJS etc.&lt;/li&gt;
&lt;li&gt;Code of Conduct template and consent check for events, talks, sponsors,
attendees, organizers, etc.&lt;/li&gt;
&lt;li&gt;iCalendar export of events as &lt;code&gt;.ics&lt;/code&gt; file in the same repository, which users
can subscribe to in their calendars&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;GitEvents Roadmap&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;More modular/flexible approach for 'auto invite', issue management, ics
generation etc.&lt;/li&gt;
&lt;li&gt;Add atom/xml/json feeds for events&lt;/li&gt;
&lt;li&gt;Discord integration&lt;/li&gt;
&lt;li&gt;Snippets to copy&amp;amp;paste the event schedule to other websites&lt;/li&gt;
&lt;li&gt;Simplify the event/talk relationship and connect everything properly&lt;/li&gt;
&lt;li&gt;Add a parser for GitHub Issue Forms instead of relying markdown frontmatter&lt;/li&gt;
&lt;li&gt;Much more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We're just getting started (in-the-middle-but-somewhat-post-pandemic?), but it
really helps getting people together and involve everyone in the process. Events
are a great way to get people together either virtual or in real life, and we're
excited to see what you can do with it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;http://github.com/gitevents&lt;/li&gt;
&lt;li&gt;https://gitevents.org&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'd love to &lt;a href="https://github.com/zentered/feedback/discussions/3"&gt;hear your feedback&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>event</category>
      <category>management</category>
      <category>issue</category>
      <category>ops</category>
    </item>
    <item>
      <title>Authenticated requests with Vite + React 18 + Hummingbird Swift API</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Tue, 28 Dec 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/zentered/authenticated-requests-with-vite-react-18-hummingbird-swift-api-3kko</link>
      <guid>https://dev.to/zentered/authenticated-requests-with-vite-react-18-hummingbird-swift-api-3kko</guid>
      <description>&lt;h2&gt;
  
  
  Motivation and Introduction
&lt;/h2&gt;

&lt;p&gt;As we're exploring &lt;a href="https://zentered.co/articles/getting-started-with-swift-server/"&gt;Swift on Server&lt;/a&gt;, we wanted to see how that integrates with our authentication solution. &lt;a href="https://vitejs.dev"&gt;Vite&lt;/a&gt; has been the hype the past months, so we were eager to try this out as well. We built a small demo project to log in to Auth0, obtain a &lt;a href="https://auth0.com/docs/security/tokens/json-web-tokens"&gt;JSON Web Token&lt;/a&gt; (JWT), and use the token to authenticate requests to the Swift API. For the API, we picked &lt;a href="https://github.com/hummingbird-project"&gt;Hummingbird&lt;/a&gt; as it's a bit lighter than &lt;a href="http://vapor.codes"&gt;Vapor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check out the code &lt;a href="https://github.com/zentered/auth0-vite-react-swift-demo"&gt;on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auth0
&lt;/h3&gt;

&lt;p&gt;As a first step, let's create an Auth0 Application and note down the variables. If you haven't signed up for Auth0 yet, you can do that for free, and then create a "Single Page Application (SPA)" in the &lt;a href="https://manage.auth0.com/dashboard"&gt;Auth0 Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pgn5cxnV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rd1p8rxeaaiz7i1oi694.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pgn5cxnV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rd1p8rxeaaiz7i1oi694.png" alt="auth0 create application screen" width="880" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To allow requests from localhost, add &lt;code&gt;http://localhost:3000&lt;/code&gt; to the allowed callback URLs, web origins and logout URLs. If you deploy this application to a cloud provider, the URLs need to be added here as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aOX87RaC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgtsgmauxevoyrltr41k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aOX87RaC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgtsgmauxevoyrltr41k.png" alt="auth0 application settings for allowed callback and origin urls" width="880" height="803"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the frontend (React), add these values into the &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VITE_APP_AUTH0_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;app-id&amp;gt;.&amp;lt;region&amp;gt;.auth0.com
&lt;span class="nv"&gt;VITE_APP_AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-auth0-client-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; you can find detailled instructions about Auth0 with React in the &lt;a href="https://auth0.com/docs/quickstart/spa/react"&gt;Quickstart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the backend (Hummingbird/Swift), we need the &lt;a href="https://auth0.com/docs/security/tokens/json-web-tokens/json-web-key-sets"&gt;"JSON Web Key Sets"&lt;/a&gt; of your application. You can find the endpoint in the Application Settings at the bottom "Advanced Settings" -&amp;gt; "Endpoints". It should look more or less like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;JWKS_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;app-id&amp;gt;.&amp;lt;region&amp;gt;.auth0.com/.well-known/jwks.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, go to "Users" and add a test/dev user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vite/React/Windi
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev"&gt;Vite&lt;/a&gt; is the "Next Generation Frontend Tooling" and we wanted to test first-hand what that means. We used an &lt;a href="https://github.com/oedotme/render"&gt;"Opinionated React Template"&lt;/a&gt; created by Omar Elhawary as a base, since it's fairly close to the structure we're used to from Next.js. This includes React 18, Vite and some other tooling. Instead of Tailwind we use &lt;a href="https://windicss.org"&gt;WindiCSS&lt;/a&gt; which has &lt;a href="https://windicss.org/integrations/vite.html"&gt;great support for Vite&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the package
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; vite-plugin-windicss windicss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the &lt;code&gt;vite.config.js&lt;/code&gt; config file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WindiCSS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plugin-windicss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;WindiCSS&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;windi&lt;/code&gt; to your &lt;code&gt;main.js/ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;virtual:windi.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the authentication, we'll use the &lt;a href="https://auth0.com/docs/quickstart/spa/react#install-the-auth0-react-sdk"&gt;Auth0 React SDK&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @auth0/auth0-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add the &lt;code&gt;Auth0Provider&lt;/code&gt; to your &lt;code&gt;main.jsx/tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;virtual:windi.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hydrateRoot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BrowserRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth0Provider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/auth0-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BrowserRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Auth0Provider&lt;/span&gt;
        &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APP_AUTH0_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APP_AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
        &lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Auth0Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;BrowserRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Element&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasChildNodes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;hydrateRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first page that's loaded is &lt;code&gt;index.jsx/tsx&lt;/code&gt;, so we'll add the &lt;code&gt;useAuth0&lt;/code&gt; helper to that page and require authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withAuthenticationRequired&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/auth0-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{...}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;withAuthenticationRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onRedirecting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Redirecting you to the login page...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The helper provides several states such as &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;isLoading&lt;/code&gt; as well as the &lt;code&gt;user&lt;/code&gt; data and &lt;code&gt;logout&lt;/code&gt; action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getAccessTokenSilently&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useAuth0&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make authenticated requests with a JWT, we'll use &lt;code&gt;getAccessTokenSilently()&lt;/code&gt; and pass the &lt;code&gt;audience&lt;/code&gt;. Prefixed with &lt;code&gt;Bearer&lt;/code&gt;, we have a valid authentication token for our API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getAccessTokenSilently&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APP_AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hummingbird (Swift) API with JWT
&lt;/h3&gt;

&lt;p&gt;In this example we don't use any unauthenticated requests; all requests to the API need to have an &lt;code&gt;Authorization&lt;/code&gt; header. The easiest way is a simple Middleware to decode the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Hummingbird&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;HummingbirdAuth&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;JWTKit&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;JWTPayloadData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;JWTPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Equatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;HBAuthenticatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;CodingKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CodingKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sub"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;expiration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"exp"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SubjectClaim&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ExpirationClaim&lt;/span&gt;
  &lt;span class="c1"&gt;// Define additional JWT Attributes here&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="nv"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;JWTSigner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyNotExpired&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;JWTAuthenticator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HBAsyncAuthenticator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;jwks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;JWKS&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;jwksUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jwksData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;contentsOf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jwksUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;jwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;JWKS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jwksData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HBRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;JWTPayloadData&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;jwtToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authBearer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;HBHTTPError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;signers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;JWTSigners&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;jwks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jwks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwtToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;JWTPayloadData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"couldn't verify token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;HBHTTPError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we're using two different ports (&lt;code&gt;3000&lt;/code&gt; for the vite client, &lt;code&gt;8080&lt;/code&gt; for the hummingbird server), we'll also need to enable Cross-Origin Resource Sharing (CORS). You can add both middlewares to your Application+configuration.swift`:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`swift&lt;br&gt;
self.middleware.add(&lt;br&gt;
  HBCORSMiddleware(&lt;br&gt;
    allowOrigin: .originBased,&lt;br&gt;
    allowHeaders: ["Accept", "Authorization", "Content-Type", "Origin"],&lt;br&gt;
    allowMethods: [.GET, .OPTIONS]&lt;br&gt;
  ))&lt;/p&gt;

&lt;p&gt;let jwtAuthenticator: JWTAuthenticator&lt;br&gt;
guard let jwksUrl = env.get("JWKS_URL") else { preconditionFailure("jwks config missing") }&lt;br&gt;
do {&lt;br&gt;
  jwtAuthenticator = try JWTAuthenticator(jwksUrl: jwksUrl)&lt;br&gt;
} catch {&lt;br&gt;
  print("JWTAuthenticator initialization failed")&lt;br&gt;
  throw error&lt;br&gt;
}&lt;br&gt;
self.middleware.add(jwtAuthenticator)&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can then use the auth0 user id in requests to request user-specific data etc.:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;swift&lt;br&gt;
let jwtPayload = request.authGet(JWTPayloadData.self)&lt;br&gt;
let userId = jwtPayload?.subject&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Hummingbird does not load &lt;code&gt;.env&lt;/code&gt; variables out of the box, so we'll use a &lt;code&gt;Makefile&lt;/code&gt; to load the environment and build/run the server:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`makefile&lt;/p&gt;

&lt;h1&gt;
  
  
  !make
&lt;/h1&gt;

&lt;p&gt;MAKEFLAGS += --silent&lt;br&gt;
include .env&lt;br&gt;
export $(shell sed 's/=.*//' .env)&lt;/p&gt;

&lt;p&gt;start:&lt;br&gt;
    swift run Server&lt;/p&gt;

&lt;p&gt;build:&lt;br&gt;
    swift build -c release&lt;/p&gt;

&lt;p&gt;install:&lt;br&gt;
    swift package resolve&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the first step, we've created the &lt;code&gt;.env&lt;/code&gt; file already for the server, so the &lt;code&gt;JWKS_URL&lt;/code&gt; should be available, otherwise &lt;code&gt;make start&lt;/code&gt; will throw an error, as the precondition fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Open two terminal windows and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
npm run dev&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to start the vite development server on port &lt;code&gt;3000&lt;/code&gt; and:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
make start&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to start the swift API on port &lt;code&gt;8080&lt;/code&gt;. Open your browser on &lt;code&gt;http://localhost:3000&lt;/code&gt; and you should be redirected to an Auth0 login screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ujx-BwJn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5qcnzjho8fsnio6klf5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ujx-BwJn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5qcnzjho8fsnio6klf5.png" alt="auth0 login screen" width="880" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After logging in, you can make an authenticated API request and get some data back:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UYpy1Ud2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5mvwl7grqckedapb9vk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UYpy1Ud2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5mvwl7grqckedapb9vk.png" alt="api results show a list of users" width="880" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;As of today, React 18 is still in beta, the initial setup was a bit tricky, but once we finally got it working, it was really pleasent. Especially the Next.js-like routing and &lt;em&gt;pages&lt;/em&gt;/&lt;em&gt;components&lt;/em&gt; structure made it very easy to transition. Vite is &lt;em&gt;super&lt;/em&gt; fast, it's simply amazing how the Hot Module Replacement (HMR) works. The SPA flow with Auth0 is quick and painless as well, and the Access Tokens are refreshed automatically whenever needed.&lt;/p&gt;

&lt;p&gt;Given we're still getting started with Swift on Server, the authentication middleware for Hummingbird was quite the challenge. We made it work with &lt;a href="https://github.com/vapor/jwt-kit"&gt;JWT-Kit&lt;/a&gt; which offers a lot of the needed functionality (especially fetching JWKS and RS256 encryption).&lt;/p&gt;

&lt;p&gt;The final outcome is a fast, reliable and strictly typed API that can be deployed on &lt;a href="https://zentered.co/articles/deploying-swift-api-on-google-cloud/"&gt;Google Cloud (CGP)&lt;/a&gt; and &lt;a href="https://zentered.co/articles/deploying-swift-api-on-aws/"&gt;Amazon Web Services (AWS)&lt;/a&gt; and a Single Page Application (SPA) that can be deployed to a simple Storage bucket like S3 or Cloud Storage. The API can be used with Cross-Origin headers, or routed with a load balancer on the same domain (ie. &lt;code&gt;/api&lt;/code&gt;). The application starts with a nice, branded login/signup window and easily integrates with social providers by just enabling them in the Auth0 console. Auth0 credentials are stored in a cookie and a JWT access token can be requested on demand when API requests are made.&lt;/p&gt;

&lt;h3&gt;
  
  
  Special Thanks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;the contributors of JWT-Kit - &lt;a href="https://github.com/vapor/jwt-kit"&gt;https://github.com/vapor/jwt-kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Adam Fowler for the Hummingbird Project - &lt;a href="https://github.com/hummingbird-project"&gt;https://github.com/hummingbird-project&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Omar Elhawary for &lt;a href="https://github.com/oedotme/render"&gt;https://github.com/oedotme/render&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the source code here: &lt;a href="https://github.com/zentered/auth0-vite-react-swift-demo"&gt;Swift API Demo on GitHub&lt;/a&gt;. If you have any questions or comments, please &lt;a href="https://twitter.com/zenteredco"&gt;reach out on Twitter&lt;/a&gt; or &lt;a href="https://github.com/zentered/zentered/discussions/new?category=ideas-feedback"&gt;start a discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>vite</category>
      <category>react</category>
      <category>auth0</category>
    </item>
    <item>
      <title>Deploying a Swift API on AWS Elastic Container Service with CodePipeline</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Tue, 14 Dec 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/zentered/deploying-a-swift-api-on-aws-elastic-container-service-with-codepipeline-3eg3</link>
      <guid>https://dev.to/zentered/deploying-a-swift-api-on-aws-elastic-container-service-with-codepipeline-3eg3</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;After &lt;a href="https://zentered.co/articles/getting-started-with-swift-server/"&gt;getting started with Swift on Server&lt;/a&gt; and &lt;a href="https://zentered.co/articles/deploying-swift-api-on-google-cloud/"&gt;deploying a Swift on Server API on Google Cloud Run with Google Cloud Build&lt;/a&gt;, we wanted to see how to get the Swift API running on Amazon Web Services (AWS). AWS offers a lot more products and flexibility, which comes at the cost of simplicity, so we created a new &lt;a href="https://www.terraform.io"&gt;Terraform&lt;/a&gt; project to consistently re-create the environment. Here's an overview of the puzzle pieces roughly in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CodeCommit&lt;/strong&gt; as a mirror of the GitHub repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeBuild&lt;/strong&gt; to build the Docker image and push it to the registry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeDeploy&lt;/strong&gt; to orchestrate the deployment and traffic allocation to ECS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodePipeline&lt;/strong&gt; to glue Commit, Build and Deploy together in a nice deployment pipeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic Container Registry (ECR)&lt;/strong&gt; to store the Docker images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic Container Service (ECS)&lt;/strong&gt; to run Docker containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic Application Load Balancer (ABN)&lt;/strong&gt; to manage the traffic between instances and allow "Blue/Green Deployment"&lt;/li&gt;
&lt;li&gt;and a few more 3-letter acronyms and other AWS products like KMS, IAM, S3, CloudWatch...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HWgl4URZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh6nsx5fvozoiasm5uzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HWgl4URZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh6nsx5fvozoiasm5uzd.png" alt="diagram showing the above mentioned AWS products" width="880" height="914"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please mind the &lt;code&gt;/api&lt;/code&gt; folder. For prototyping we've decided to work in a single repo, while the server bits are stored in the &lt;code&gt;/api&lt;/code&gt; subfolder (which makes things again a little more difficult, as this needs to be specified in various places).&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR; Mistakes made, 🤦 and lessons learned
&lt;/h2&gt;

&lt;p&gt;You can find our &lt;a href="https://github.com/zentered/terraform-ecs-blue-green-pipeline"&gt;Terraform project on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for ECS Blue/Green builds, the "image definition" is not "imagedefinitions.json" but &lt;code&gt;imageDetail.json&lt;/code&gt;: &lt;code&gt;- printf '{"ImageURI":"%s"}' ${REPOSITORY_URI}:latest} &amp;gt; imageDetail.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;to deploy from CodePipeline to ECS, the "blue/green" strategy is required&lt;/li&gt;
&lt;li&gt;CodePipeline uses S3 to pass build artifacts from one step to the next&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;taskdef.json&lt;/code&gt; and &lt;code&gt;appspec.yaml&lt;/code&gt; are necessary build artifacts from the build to the deploy step&lt;/li&gt;
&lt;li&gt;it's &lt;code&gt;appspec.yaml&lt;/code&gt; (y*&lt;em&gt;a&lt;/em&gt;*ml with the 'a') not &lt;code&gt;appspec.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker container port (ie &lt;code&gt;8080&lt;/code&gt;) is used everywhere except for the Load Balancer &lt;em&gt;Listener&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Fargate&lt;/code&gt; requires network mode &lt;code&gt;awsvpc&lt;/code&gt; and public IPs, or private IPs and a lot more configuration with firewalls, networks etc.&lt;/li&gt;
&lt;li&gt;IAM permissions are a mess&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;snake_case&lt;/code&gt; should be used for all Terraform names&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting down to the nitty gritty
&lt;/h2&gt;

&lt;p&gt;Setting up this project was all but smooth. There are a lot of little details and a single wrong number can cost you hours of debugging. The AWS error messages are misleading, the user/developer experience a mess, each of the products uses different UIs, all in all very chaotic. At least with Terraform we're able to write "Infrastructure as Code" and provide a working Terraform project that you can use as a starting point. Some product specific information first:&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS CodeCommit
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CodeBuild&lt;/code&gt; uses GitHub OAuth, which gives Amazon access to all public and private repos. We're a little paranoid, so that's not something we wanted to do. Instead, we created a CodeCommit repo and push the changes there, until AWS switches to the new GitHub App experience, which lets you choose individual repos.&lt;/p&gt;

&lt;p&gt;In order to push to CodeBuild, you need to add your ssh public key to your IAM user and add CodeCommit origin to your &lt;code&gt;git config&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CodeCommit setup: &lt;a href="https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-ssh-unixes.html?icmpid=docs_acc_console_connect_np"&gt;https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-ssh-unixes.html?icmpid=docs_acc_console_connect_np&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IAM console "security credentials": &lt;a href="https://console.aws.amazon.com/iam/home#/users/?section=security_credentials"&gt;https://console.aws.amazon.com/iam/home#/users/?section=security_credentials&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can probably skip this with the &lt;a href="https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-github.html"&gt;AWS Connector for GitHub&lt;/a&gt; where you can connect a GitHub Repo as Source in the Pipeline. We're still working that out with Terraform.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS CodeBuild
&lt;/h3&gt;

&lt;p&gt;CodeBuild was one of the easier tools to use. Just add your &lt;code&gt;buildspec.yml&lt;/code&gt;. Important are the build artifacts that are exported to S3 and used by the deploy step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Elastic Container Registry &amp;amp; Elastic Container Service (ECS)
&lt;/h3&gt;

&lt;p&gt;To get a head start for your builds, you can push your &lt;code&gt;latest&lt;/code&gt; image to ECR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ecr get-login-password --region &amp;lt;region&amp;gt; | docker login --username AWS --password-stdin &amp;lt;account_id&amp;gt;.dkr.ecr.&amp;lt;region&amp;gt;.amazonaws.com
docker push &amp;lt;account_id&amp;gt;.dkr.ecr.&amp;lt;region&amp;gt;.amazonaws.com/&amp;lt;image&amp;gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ECS was the worst part to get up and running, since there are a lot of different components playing together. There are containers, tasks, services, task definitions, image definitions and more. If you encounter any issues, it's worth looking into the service, then tasks, to see if the container boots up correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Deploy
&lt;/h2&gt;

&lt;p&gt;We've published the entire Terraform project here: &lt;a href="https://github.com/zentered/terraform-ecs-blue-green-pipeline"&gt;Terraform ECS Blue/Green Deployments with CodePipeline&lt;/a&gt;. You need to &lt;a href="https://www.terraform.io/downloads.html"&gt;download the Terraform CLI&lt;/a&gt;. If you're new to AWS and you don't have the AWS CLI installed, you can find &lt;a href="https://aws.amazon.com/cli/"&gt;instructions here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform validate
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform outputs the url of the load balancer, you should be able to navigate to that URL and get the 'hello world' response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean up:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to fork/clone the repo and adjust it to your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;AWS comes with a lot of benefits and great features, but the UI across the products is inconsistent and there are dozens of configuration options, permissions and other things to take care of and its easy to miss an important detail. Setting up the project in Terraform makes things a little easier, but it still takes time to write down all the configs. There are some pre-built Terraform modules for AWS which we didn't use, as we wanted to learn how things worked under the hood. In comparison, &lt;a href="https://zentered.co/articles/deploying-swift-api-on-google-cloud/"&gt;deployment to GCP&lt;/a&gt; was definitely a lot easier and faster to set things up. On GCP there are a lot of "beta" products though and things can change, or sometimes don't work as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Special thanks and further reading ...
&lt;/h2&gt;

&lt;p&gt;Special thanks to "vinycoolguy2015", "snow-dev" and "capital one" for their articles and resources on the topic. Here's a list of links to articles and repos that helped put together this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vinycoolguy2015/awslambda/tree/master/terraform_ecs_bluegreen"&gt;https://github.com/vinycoolguy2015/awslambda/tree/master/terraform_ecs_bluegreen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/amazon-ecs-fullstack-app-terraform"&gt;https://github.com/aws-samples/amazon-ecs-fullstack-app-terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snow-dev.com/posts/ecs-cd-with-codepipeline-in-terraform.html"&gt;https://snow-dev.com/posts/ecs-cd-with-codepipeline-in-terraform.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/snowiow/green-blue-ecs-example/blob/master/buildspec.yml"&gt;https://github.com/snowiow/green-blue-ecs-example/blob/master/buildspec.yml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/codepipeline/latest/userguide/ecs-cd-pipeline.html"&gt;https://docs.aws.amazon.com/codepipeline/latest/userguide/ecs-cd-pipeline.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/capital-one-tech/seamless-blue-green-deployment-using-aws-codedeploy-4c36c0bbeef4"&gt;https://medium.com/capital-one-tech/seamless-blue-green-deployment-using-aws-codedeploy-4c36c0bbeef4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/flexelem/aws-samples/tree/master/nodejs-codepipeline"&gt;https://github.com/flexelem/aws-samples/tree/master/nodejs-codepipeline&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading! You can &lt;a href="https://github.com/zentered/terraform-ecs-blue-green-pipeline"&gt;check out all the code on GitHub&lt;/a&gt; and read more about our "Swift on Server" series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zentered.co/articles/deploying-swift-api-on-google-cloud/"&gt;Deploying a Swift API on Google Cloud Run with Google Cloud Build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zentered.co/articles/getting-started-with-swift-server/"&gt;Getting Started with Swift on Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any questions or comments, please &lt;a href="https://twitter.com/zenteredco"&gt;reach out on Twitter&lt;/a&gt; or &lt;a href="https://github.com/zentered/zentered/discussions/new?category=ideas-feedback"&gt;start a discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftonserver</category>
      <category>aws</category>
    </item>
    <item>
      <title>Deploying a Swift API on Google Cloud Run with Google Cloud Build</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Fri, 03 Dec 2021 19:59:30 +0000</pubDate>
      <link>https://dev.to/zentered/deploying-a-swift-api-on-google-cloud-run-with-google-cloud-build-26b0</link>
      <guid>https://dev.to/zentered/deploying-a-swift-api-on-google-cloud-run-with-google-cloud-build-26b0</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Recently we &lt;a href="https://zentered.co/articles/getting-started-with-swift-server/"&gt;started a Swift API proof of concept&lt;/a&gt; and wanted to see if we can deploy the service with "the tools we know" from Node.js: continuous integration in the cloud and deployment to serverless / managed k8s platforms. TL;DR: It works! And very similar to working with Node.js services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;Let's start with the Dockerfile, the current standard for k8s/Cloud Native services. If you created your new awesome Swift API with the Vapor CLI/template, there's a Dockerfile already in the folder. We decided to remove the package updates to speed up the builds a bit. We also removed the user/group specifics and rely on system defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; swift:5.5-focal as build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./Package.* ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;swift package resolve
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;swift build &lt;span class="nt"&gt;-c&lt;/span&gt; release

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /staging&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;swift build &lt;span class="nt"&gt;--package-path&lt;/span&gt; /build &lt;span class="nt"&gt;-c&lt;/span&gt; release &lt;span class="nt"&gt;--show-bin-path&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/Run"&lt;/span&gt; ./
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /build/Public &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; /build/Public ./Public &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; a-w ./Public&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /build/Resources &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; /build/Resources ./Resources &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; a-w ./Resources&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; swift:5.5-focal-slim&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive &lt;span class="nv"&gt;DEBCONF_NONINTERACTIVE_SEEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install &lt;/span&gt;ca-certificates

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /staging /app&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["./Run"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We still need to make sure the &lt;code&gt;ca-certificates&lt;/code&gt; package is installed, as PlanetScale uses those. If you use a different database, you might be able to skip this step too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact Registry
&lt;/h2&gt;

&lt;p&gt;Google deprecated the Container Registry in favor of the Artifact Registry, which can also handle Node.js, Java and other packages, not just Docker containers. You have to &lt;a href="https://console.cloud.google.com/artifacts"&gt;enable the Artifact Registry&lt;/a&gt; for your project. There are a few minor changes to the URLs. If you want to connect locally to the registry (push/pull), you need to authenticate for your region. You can read more here: &lt;a href="https://cloud.google.com/artifact-registry/docs/transition/changes-docker"&gt;https://cloud.google.com/artifact-registry/docs/transition/changes-docker&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Build
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y0YbEpDq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1t1f4dsc9c3pz53zfq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y0YbEpDq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1t1f4dsc9c3pz53zfq9.png" alt="screenshot of the cloud build history" width="880" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For continous deployment of our Swift API, we're using Cloud Build pretty much the same way as for a Node.js API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;build the container image&lt;/li&gt;
&lt;li&gt;deploy the image to Cloud Run&lt;/li&gt;
&lt;li&gt;cache the image in the Artifact Registry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The easiest way to set this up is to go to &lt;a href="https://console.cloud.google.com/cloud-build/triggers"&gt;Cloud Build Triggers&lt;/a&gt; and &lt;a href="https://console.cloud.google.com/cloud-build/triggers/add"&gt;"Create a Trigger"&lt;/a&gt;. Set a name, "Push to branch", and pick the Source Repository. You may need to authenticate to GitHub/Bitbucket and set permissions for Google Cloud Platform to access your repo. At the end you should set the "Substitution variables", which are environment variables that you can inject in the runtime, such as the &lt;code&gt;DATABASE_HOST&lt;/code&gt; or other information. We're going to check out how to use the Secret Manager with Swift in a future post. See the screenshot for the trigger settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9HLhsekF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks68rx5ym3tktiou4xlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9HLhsekF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks68rx5ym3tktiou4xlt.png" alt="screenshot of cloud build trigger settings" width="763" height="1918"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the trigger to work, a &lt;code&gt;cloudbuild.yml&lt;/code&gt; file is required in the root of the project. We first try to fetch the latest image for caching, then we build the &lt;code&gt;Dockerfile&lt;/code&gt;, push it to the registry and deploy the service to Cloud Run. Important here is to set the &lt;code&gt;--port=8080&lt;/code&gt; parameter, as Vapor doesn't use the &lt;code&gt;PORT&lt;/code&gt; environment variable at the moment. &lt;a href="https://cloud.google.com/run/docs/locations"&gt;Here is a list of Cloud Run regions&lt;/a&gt;, we recommend &lt;code&gt;us-central1&lt;/code&gt; or &lt;code&gt;europe-west1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately the builds take quite long, be prepared for 20-30 minute build times. To save yourself time and energy, build (&lt;code&gt;docker build . -t swift-server&lt;/code&gt;) and test run (&lt;code&gt;docker run -p 8080:8080 swift-server&lt;/code&gt;) the image on your local machine first, and make sure the server starts correctly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;cloudbuild.yml, replace &lt;code&gt;&amp;lt;repo-name&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;image-name&amp;gt;&lt;/code&gt;:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bash'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-c'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;docker pull ${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:latest || exit 0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-t&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:$SHORT_SHA&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-t&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:latest&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--cache-from&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:latest&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;push'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:$SHORT_SHA'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/gcloud'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;repo-name&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--image=${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:$SHORT_SHA&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--port=8080&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--region=us-central1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--memory=512Mi&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--platform=managed&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--allow-unauthenticated&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--min-instances=0&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max-instances=5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--set-env-vars&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;^;^ENV=production; DATABASE_HOST=${_DATABASE_HOST}; DATABASE_PORT=${_DATABASE_PORT}; DATABASE_USERNAME=${_DATABASE_USERNAME}; DATABASE_PASSWORD=${_DATABASE_PASSWORD}; DATABASE_NAME=${_DATABASE_NAME}&lt;/span&gt;
&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:$SHORT_SHA'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${_REGISTRY}/$PROJECT_ID/&amp;lt;repo-name&amp;gt;/&amp;lt;image-name&amp;gt;:latest'&lt;/span&gt;
&lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2000s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; See &lt;a href="https://cloud.google.com/run/docs/configuring/environment-variables#setting"&gt;environment variables&lt;/a&gt; for more information on &lt;code&gt;^;^...&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Run
&lt;/h2&gt;

&lt;p&gt;In this article we use &lt;a href="https://cloud.google.com/run/"&gt;Cloud Run&lt;/a&gt;, a managed Kubernetes product on Google Cloud Platform. It allows cloud native, serverless deployments of Docker images directly from Cloud Build.&lt;/p&gt;

&lt;p&gt;If you have the &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;cloudbuild.yml&lt;/code&gt; files in your repo, and Cloud Build finished the build job with the little green ✅, your Cloud Run service should already be deployed and running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WjnJK55F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ygly6p960mlpktd1jzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WjnJK55F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ygly6p960mlpktd1jzt.png" alt="screenshot of cloud run overview page" width="880" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the service and you should see the service details, including the URL of your app (ie. &lt;code&gt;swift-api-demo-123.run.app.&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vcwSosBI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6v2g3bzq06jqtiosb04t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vcwSosBI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6v2g3bzq06jqtiosb04t.png" alt="screenshot of cloud run details page" width="880" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking the link should show you a simple but satisfying &lt;code&gt;It works!&lt;/code&gt; in the browser. If you add &lt;code&gt;/todos&lt;/code&gt; to the URL, you should see the item you added during development, or you can &lt;code&gt;POST&lt;/code&gt; to the address to create a new item. In our Cloud Build config we defined &lt;code&gt;--min-instances=0&lt;/code&gt; which means the app will go into standby after ~15 minutes if there are no requests, which is very cost effective. You can increase the number to have "always on" services. &lt;code&gt;--max-instances&lt;/code&gt; sets the maximum number of instances, for auto scaling. The region, memory, cpu settings etc. can be adjusted either with substitution variables or hard-coded in the &lt;code&gt;cloudbuild&lt;/code&gt; file. Substitutions are great if you want to deploy to different regions, for example.&lt;/p&gt;

&lt;p&gt;As this proof of concept is successful, with Cloud Run we can also perform gradual rollouts such as canary deployments or blue/green deployments of your Swift API, run everything on your own domain and create high availability services 🥳.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This is very exciting. We're essentially using the same tools and workflows we already use for production apps in Node.js, and get the same logging, monitoring and scaling for our apps. To sum up the setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; and build/run the app locally to verify it works&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;cloudbuild.yml&lt;/code&gt; to the repo in the project root folder&lt;/li&gt;
&lt;li&gt;Go to the Cloud Build history and check if your build is green&lt;/li&gt;
&lt;li&gt;Go to the Cloud Run overview to check the status of your service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Afterwards the workflow for developers is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write code&lt;/li&gt;
&lt;li&gt;Commit, push and merge&lt;/li&gt;
&lt;li&gt;Wait...&lt;/li&gt;
&lt;li&gt;Check out your feature live in production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks for reading! You can &lt;a href="https://github.com/zentered/swift-vapor-demo"&gt;check out all the code on GitHub&lt;/a&gt;. If you have any questions or comments, please &lt;a href="https://twitter.com/zenteredco"&gt;reach out on Twitter&lt;/a&gt; or &lt;a href="https://github.com/zentered/zentered/discussions/new?category=ideas-feedback"&gt;start a discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll be working on authentication, performance and semantic versioning next, stay tuned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/swift-server/guides/blob/main/docs/deployment.md"&gt;Swift on Server: Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/cloud-run-story-serverless-containers"&gt;Cloud Run: What no one tells you about Serverless (and how it's done)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/build/docs/automating-builds/create-manage-triggers"&gt;Cloud Build: Creating and managing build triggers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>googlecloud</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Getting Started with Swift on Server</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Fri, 03 Dec 2021 19:47:59 +0000</pubDate>
      <link>https://dev.to/zentered/getting-started-with-swift-on-server-1if7</link>
      <guid>https://dev.to/zentered/getting-started-with-swift-on-server-1if7</guid>
      <description>&lt;p&gt;We've been working with Node.js for over 10 years now and decided to pick up a new challenge. For "strong typing" features, people are exploring &lt;a href="https://www.rust-lang.org"&gt;Rust&lt;/a&gt; and &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; as alternatives to Node.js with TypeScript. Swift on Server has been around for a few years now, and Swift is an interesting language to learn for mobile development. It feels quite similar to JavaScript with the new async/await features and is easily readable.&lt;/p&gt;

&lt;p&gt;There are some interesting http frameworks (think Express.js, Fastify, etc.) to simplify working with &lt;code&gt;SwiftNIO&lt;/code&gt; such as &lt;a href="https://vapor.codes/"&gt;Vapor&lt;/a&gt;, &lt;a href="https://github.com/hummingbird-project"&gt;Hummingbird&lt;/a&gt; and &lt;a href="https://www.perfect.org/"&gt;Perfect&lt;/a&gt;. There's even a &lt;a href="https://github.com/FeatherCMS/feather"&gt;full CMS (Feather)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.swift.org/sswg/"&gt;Swift Server Work Group&lt;/a&gt; names a few &lt;a href="https://www.swift.org/server/#why-swift-on-server"&gt;benefits on why Swift is great&lt;/a&gt; as a server-side language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small footprint&lt;/li&gt;
&lt;li&gt;Quick startup time&lt;/li&gt;
&lt;li&gt;Deterministic performance&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Small footprint: One of the main goals of a modern cloud platform is to maximize resource utilization by efficiently packing services into a single machine. Cloud services built with Swift have a small memory footprint (measured in MB)–especially when compared to other popular server languages with automatic memory management. Services built with Swift are also CPU-efficient, given the language’s focus on performance.&lt;/p&gt;

&lt;p&gt;Quick startup time: Swift-based applications start quickly since there are almost no warm up operations. This makes Swift a great fit for cloud services, which are often re-scheduled onto new VMs or containers to address platform formation changes. Using Swift also helps streamline continuous delivery pipelines, since you incur less wait time for new versions of the service fleet to come online. Finally, quick boot times make Swift a perfect fit for serveless applications such as Cloud Functions or Lambda with negligible cold start times.&lt;/p&gt;

&lt;p&gt;Deterministic performance: Swift’s use of ARC (instead of tracing garbage collection) and its lack of JIT gives it an important edge in the cloud services space. While tracing garbage collection technologies have vastly improved in the last few years, they still compete with the application for resources which triggers non-deterministic performance. The absence of JIT means no runtime optimization or de-optimization. It’s challenging to debug non-deterministic performance, and language-induced non-deterministic performance can both confuse and mask application-level performance issues that could otherwise be addressed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those all sound great, so we wanted to see if we can use modern, Cloud Native tools to build and deploy a Swift Server application. But first we needed to build one. We decided to go with Vapor and &lt;a href="https://planetscale.com"&gt;PlanetScale&lt;/a&gt;, to add some complexity to the regular "Hello World" app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Swift and Vapor
&lt;/h3&gt;

&lt;p&gt;On macOS, if you have the Developer Tools and Xcode installed, you should be good to go already. You can check if Swift is properly installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  » swift &lt;span class="nt"&gt;--version&lt;/span&gt;
swift-driver version: 1.26.9 Apple Swift version 5.5.1 &lt;span class="o"&gt;(&lt;/span&gt;swiftlang-1300.0.31.4 clang-1300.0.29.6&lt;span class="o"&gt;)&lt;/span&gt;
Target: arm64-apple-macosx12.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have Swift installed yet, follow the &lt;a href="https://www.swift.org/getting-started/#installing-swift"&gt;Installing Swift&lt;/a&gt; guide for macOS, Linux or Windows. You'll also need &lt;a href="https://docs.vapor.codes/4.0/install/macos/"&gt;Vapor&lt;/a&gt;, which you can easily install with brew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;vapor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PlanetScale
&lt;/h3&gt;

&lt;p&gt;Install the &lt;a href="https://docs.planetscale.com/reference/planetscale-environment-setup"&gt;PlanetScale CLI&lt;/a&gt;. For macOS, via brew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;planetscale/tap/pscale mysql-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.planetscale.com/reference/planetscale-cli"&gt;Full PlanetScale CLI reference&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your first Swift on Server / Vapor project
&lt;/h2&gt;

&lt;p&gt;This is as simple as &lt;code&gt;npm init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vapor new swift-api-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To go beyond the hello world steps, let's &lt;strong&gt;install Fluent&lt;/strong&gt; for MySQL and &lt;strong&gt;skip Leaf&lt;/strong&gt; during the project initialization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;swift-api-demo
open Package.swift
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open Xcode and download/install vapor dependencies. You should see the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5fJtJUbt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1getbxttsfdp509cz7r7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5fJtJUbt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1getbxttsfdp509cz7r7.png" alt="screenshot of Xcode with the initial scaffolding of a new project" width="880" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now simply hit the "Run" button (▶︎, or &lt;code&gt;⌘R&lt;/code&gt;). In the Xcode terminal you should see a notice that the server has started (&lt;code&gt;[ NOTICE ] Server starting on http://127.0.0.1:8080&lt;/code&gt;). And voilà, you can curl your server instance:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; » curl localhost:8080
It works!%
 » curl localhost:8080/hello
Hello, world!%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  VScode
&lt;/h3&gt;

&lt;p&gt;Instead of XCode, you can also write Swift applications in VSCode. There is a &lt;a href="https://github.com/swift-server/vscode-swift"&gt;vscode extension&lt;/a&gt; available for the language support. We love "auto format on save", which is a little tricky, but can be achieved by downloading and compiling &lt;a href="https://github.com/apple/swift-format#command-line-usage"&gt;swift-format&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.50500.0  &lt;span class="c"&gt;# replace this with the version you need&lt;/span&gt;
git clone https://github.com/apple/swift-format.git
&lt;span class="nb"&gt;cd &lt;/span&gt;swift-format
git checkout &lt;span class="s2"&gt;"tags/&lt;/span&gt;&lt;span class="nv"&gt;$VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
swift build &lt;span class="nt"&gt;-c&lt;/span&gt; release
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /usr/local/bin/swift-format .build/release/swift-format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line will create a symlink in your binaries folder (macOS specific, you may need to use a different path). Afterwards, &lt;code&gt;format on save&lt;/code&gt; should work wth the &lt;a href="https://marketplace.visualstudio.com/items?itemName=vknabel.vscode-swiftformat"&gt;SwiftFormat extension&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the PlanetScale Database
&lt;/h2&gt;

&lt;p&gt;After installing the &lt;a href="https://github.com/planetscale/cli"&gt;PlanetScale CLI&lt;/a&gt; you need to log in, create a database and a password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    pscale auth login
    pscale database create swift-server-db
    pscale password create swift-server-db main production-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NP4U4KXg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpt30l7phnreb7o6ssy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NP4U4KXg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpt30l7phnreb7o6ssy6.png" alt="screenshot of the promote to production screen on planetscale" width="429" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default database has no "production" branch, which means schema changes can be applied directly on &lt;code&gt;main&lt;/code&gt;. Once you &lt;em&gt;promote&lt;/em&gt; your branch to production, you need to follow a "feature branch flow", just like in Git:&lt;/p&gt;

&lt;p&gt;Create a branch, then open a SQL shell to create a new table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    pscale branch create swift-server-db todos
    pscale shell swift-server-db todos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're creating the table &lt;code&gt;todos&lt;/code&gt; with an autogenerated id and a title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`todos`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`title`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`created_at`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nv"&gt;`updated_at`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_0900_ai_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These changes are now in a branch. You can open a Deploy Request and merge it into &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pscale deploy-request create swift-server-db todos
pscale deploy-request deploy swift-server-db 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to use &lt;a href="https://dbeaver.io/"&gt;DBeaver&lt;/a&gt; or any other MySQL UI, you can connect to the branch directly with &lt;code&gt;pscale connect &amp;lt;db-name&amp;gt; &amp;lt;branch-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting the Pieces
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Identifiers (IDs)
&lt;/h3&gt;

&lt;p&gt;Vapor/Fluent work with &lt;code&gt;UUID&lt;/code&gt;s as default. MySQL doesn't have native support for those, so it's recommended to switch to &lt;em&gt;Integer&lt;/em&gt; IDs:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources/App/Models/Todo.swift&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Fluent&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Vapor&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"todos"&lt;/span&gt;

    &lt;span class="kd"&gt;@ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="kd"&gt;@Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vapor with PlanetScale
&lt;/h3&gt;

&lt;p&gt;After creating a password on PlanetScale, you should be able to &lt;a href="https://docs.planetscale.com/reference/secure-connections"&gt;Connect securely&lt;/a&gt; to your database. You can find the credentials (host, user, password, db name) on your Dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3Rnmh4vI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5jndt0x8hfic18v9ero.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3Rnmh4vI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5jndt0x8hfic18v9ero.png" alt="planetscale connection settings" width="581" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default database has no "production" branch, which means schema changes can be applied directly on &lt;code&gt;main&lt;/code&gt;. Once you &lt;em&gt;promote&lt;/em&gt; your branch to production, you need to follow a "feature branch flow", just like in Git:&lt;/p&gt;

&lt;p&gt;Create a branch, then open a SQL shell to create a new table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    pscale branch create swift-server-db todos
    pscale shell swift-server-db todos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're creating the table &lt;code&gt;todos&lt;/code&gt; with an autogenerated id and a title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`todos`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`title`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`created_at`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nv"&gt;`updated_at`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_0900_ai_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These changes are now in a branch. You can open a Deploy Request and merge it into &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pscale deploy-request create swift-server-db todos
pscale deploy-request deploy swift-server-db 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to use &lt;a href="https://dbeaver.io/"&gt;DBeaver&lt;/a&gt; or any other MySQL UI, you can connect to the branch directly with &lt;code&gt;pscale connect &amp;lt;db-name&amp;gt; &amp;lt;branch-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting the Pieces
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Identifiers (IDs)
&lt;/h3&gt;

&lt;p&gt;Vapor/Fluent work with &lt;code&gt;UUID&lt;/code&gt;s as default. MySQL doesn't have native support for those, so it's recommended to switch to &lt;em&gt;Integer&lt;/em&gt; IDs:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources/App/Models/Todo.swift&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Fluent&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Vapor&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"todos"&lt;/span&gt;

    &lt;span class="kd"&gt;@ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="kd"&gt;@Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vapor with PlanetScale
&lt;/h3&gt;

&lt;p&gt;After creating a password on PlanetScale, you should be able to &lt;a href="https://docs.planetscale.com/reference/secure-connections"&gt;Connect securely&lt;/a&gt; to your database. You can find the credentials (host, user, password, db name) on your Dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YcApmG_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4gtgrmaegdlhavchioz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YcApmG_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4gtgrmaegdlhavchioz.png" alt="xcode console log without errors" width="832" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;Overall we're pretty excited how things have developed in the &lt;em&gt;Swift on the Server&lt;/em&gt; world and how &lt;a href="https://vapor.codes"&gt;Vapor&lt;/a&gt; works. There are some really cool features coming up in Swift 5.6 and everything is under active development. The &lt;a href="http://vapor.team"&gt;Vapor Community on Discord&lt;/a&gt; is super friendly and helpful!&lt;/p&gt;

&lt;p&gt;As a Proof of Concept, we're able to run a http service and connect to a MySQL database. We can store and retrieve data for a simple todo list. That's all pretty nice, but it's still just "localhost" development. We wanted to know how to get this up into the cloud, so we wrote another article on &lt;a href="https://zentered.co/articles/deploying-swift-api-on-google-cloud/"&gt;how to deploy your swift api to the cloud&lt;/a&gt;. TL;DR: this is pretty simple and we were able to use Cloud Native tooling such as Cloud Build and Cloud Run to automate the entire deployment pipeline from GitHub to Production.&lt;/p&gt;

&lt;p&gt;You can find the source code here: &lt;a href="https://github.com/zentered/swift-vapor-demo"&gt;Swift API Demo on GitHub&lt;/a&gt;. If you have any questions or comments, please &lt;a href="https://twitter.com/zenteredco"&gt;reach out on Twitter&lt;/a&gt; or &lt;a href="https://github.com/zentered/zentered/discussions/new?category=ideas-feedback"&gt;start a discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TODO
&lt;/h2&gt;

&lt;p&gt;A few things we need to do some more research on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write tests and implement a TDD workflow&lt;/li&gt;
&lt;li&gt;How can we run Vapor Migrations on PlanetScale?&lt;/li&gt;
&lt;li&gt;How can we use/automate semver tags with XCode?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.swift.org/server/"&gt;Swift on Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vapor.codes/4.0/"&gt;Generic Docs for Vapor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vapor.codes/4.0/fluent/overview/"&gt;Fluent Docs for databases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vapor.codes/4.0/authentication/"&gt;Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://vapor.team"&gt;Vapor Chat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>beginners</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Serverless v3.0.0</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Tue, 23 Nov 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/zentered/serverless-v300-1c68</link>
      <guid>https://dev.to/zentered/serverless-v300-1c68</guid>
      <description>&lt;h2&gt;
  
  
  Breaking Changes: v2.0.0 to v3.0.0
&lt;/h2&gt;

&lt;p&gt;Some time ago we wrote about &lt;a href="https://zentered.co/articles/serverless-v2-0-0/"&gt;Serverless deployments v2.0.0&lt;/a&gt; and a lot has changed and improved since then, so it's time for an update.&lt;/p&gt;

&lt;p&gt;One of the biggest improvements to the workflow is &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;. No need for external CI/CD systems to generate new versions any more. GitHub also &lt;a href="https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases"&gt;improved Releases&lt;/a&gt;, and &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt; reached v1.0.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continous Deployment of Serverless Applications
&lt;/h2&gt;

&lt;p&gt;Let's walk through the details of a fully automated continous integration and deployment system with all those fancy new toys. The one thing that hasn't changed since the last article is our Git workflow: we recommend the &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow"&gt;Git Feature Branch Workflow&lt;/a&gt;. The merge strategy doesn't matter much, rebase, squash and merge all work with the following tools and flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Conventional Commits
&lt;/h3&gt;

&lt;p&gt;We use &lt;a href="https://github.com/semantic-release/semantic-release"&gt;Semantic Release&lt;/a&gt; to calculate version numbers based on commit messages. The 'de facto' standard for these commit messages has been defined in &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt;. In short, your commit messages start with a keyword, a scope and then a short description of the change. Commit messages should be written in present tense ("closes", not "closed"):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fix(accounts): calculation error on totals, closes #123&lt;/code&gt; to create a &lt;code&gt;Patch Release&lt;/code&gt; or&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feat(users): create and update user details, closes #123&lt;/code&gt; to create a &lt;code&gt;Minor Release&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find a &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;quick summary here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Semantic Release and GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Actions is a CI/CD product from GitHub. You can define "workflows" as simple &lt;code&gt;yml&lt;/code&gt; files to go through certain steps such as linting, testing, building etc. They work for pretty much any project / programming language. Every Action ususally has a trigger ("on") and jobs. You can read more about &lt;a href="https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows"&gt;Events that trigger workflows&lt;/a&gt;, &lt;a href="https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs-or-python"&gt;Building and testing Node.js Actions&lt;/a&gt; or even &lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action"&gt;Custom JavaScript Actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We usually use a simple "test, build &amp;amp; release" Action, for example for our integration Actions like &lt;a href="https://github.com/zentered/cloudflare-preview-url/blob/main/.github/workflows/publish.yml"&gt;Cloudflare Preview URL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Below you can find a slightly more complex Action that restricts permissions and runs checkout, npm install, linter, codestyle checks, tests, coverage reports and semantic release. See &lt;a href="https://zentered.co/articles/hardening-github-actions-in-private-repositories/"&gt;Hardening GitHub Actions&lt;/a&gt; for more information about security/permission steps taken in this Action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;./github/workflows/publish.yml&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Semantic Release&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
  &lt;span class="na"&gt;checks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;deployments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;repository-projects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
  &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
  &lt;span class="na"&gt;statuses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;

    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2.4.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci --ignore-scripts&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Linter&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Codestyle&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run codestyle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DISABLE_REQUEST_LOGGING&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report coverage&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v2.1.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CODECOV_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Semantic Release&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx semantic-release&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic line to convert our Conventional Commit messages into Releases is &lt;code&gt;run: npx semantic-release&lt;/code&gt;. This will run semantic-release after the tests, lint etc. are done and generate a release with fancy notes based on the commits that have been merged. For example, if you currently have a &lt;code&gt;v1.2.0&lt;/code&gt; deployed, and merge a &lt;code&gt;fix&lt;/code&gt;, semantic-release will publish &lt;code&gt;v1.2.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://github.com/zentered/cloudflare-preview-url/releases"&gt;see how Semantic Releases look like&lt;/a&gt; in our open source repos. The important piece here is, that each release also comes with a &lt;code&gt;tag&lt;/code&gt;. This is interesting and useful for a variety of reasons, for example compliance ("what has been deployed when?"), and of course the developer experience (DX). You can see the currently deployed version in each environment, without checking cryptic 6-digit SHAs. The tag is also the ingredient required for the next step:&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Deployment
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://cloud.google.com/build"&gt;Google Cloud Build&lt;/a&gt; (or &lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/create-project-console.html"&gt;AWS CodeBuild&lt;/a&gt; or &lt;a href="https://azure.microsoft.com/en-us/services/devops/pipelines/"&gt;Azure Pipelines&lt;/a&gt;) you can select a source repository for deployments and pick "tags" as a build trigger. &lt;code&gt;.v*&lt;/code&gt; for example will deploy every new release. If you want to deploy a &lt;em&gt;Major&lt;/em&gt; release in a new environment, you can switch the previous trigger settings to &lt;code&gt;.v1.*&lt;/code&gt; to only deploy &lt;code&gt;v1&lt;/code&gt; releases, but not &lt;code&gt;v2&lt;/code&gt;. This is a nice way to test breaking changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lOjiDVTH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6rzkh80299ng1iqmu7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lOjiDVTH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6rzkh80299ng1iqmu7b.png" alt="screenshot of cloud build trigger settings" width="565" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloud Build allows you to continously deploy your application. On GCP this works with a &lt;code&gt;cloudbuild.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bash'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-c'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;docker pull us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest || exit 0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-t&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-t&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--cache-from&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;push'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/gcloud'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api-demo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--image=us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--region=us-central1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--memory=256Mi&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--platform=managed&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--allow-unauthenticated&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--min-instances=0&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max-instances=5&lt;/span&gt;
&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest'&lt;/span&gt;
&lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1800s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$TAG_NAME&lt;/code&gt; is the version number, so all your images and deployments have a semantic version number and you can clearly identify what's running where. There are four steps here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;fetch last image from cache (if there's a cached image)&lt;/li&gt;
&lt;li&gt;build the code and Dockerfile&lt;/li&gt;
&lt;li&gt;push the image to the registry&lt;/li&gt;
&lt;li&gt;deploy the image on Cloud Run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; We're using the new &lt;a href="https://cloud.google.com/artifact-registry/"&gt;Artifact Registry&lt;/a&gt; instead of the deprecated Container Registry, so the registry urls are slightly different. You can create &lt;a href="https://console.cloud.google.com/artifacts/create-repo"&gt;your Artifact Repository here&lt;/a&gt; and adjust the registry region in the cloudbuild file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go live on managed k8s
&lt;/h3&gt;

&lt;p&gt;Google Cloud, AWS and Azure all have managed &lt;a href="https://kubernetes.io"&gt;Kubernetes&lt;/a&gt; products which are great to focus on software development and leave the k8s configs, helm charts and autoscale yamls to the &lt;em&gt;DevOps Pros&lt;/em&gt;. In the previous section we defined a step to deploy to Cloud Run. For GCP, you may need to enable the API, billing etc. on your project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable &lt;a href="https://console.cloud.google.com/billing"&gt;Billing&lt;/a&gt; (requires a credit card)&lt;/li&gt;
&lt;li&gt;enable the &lt;a href="https://console.cloud.google.com/apis/api/cloudbuild.googleapis.com/overview"&gt;Cloud Build API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;enable the &lt;a href="https://console.cloud.google.com/apis/api/run.googleapis.com/overview"&gt;Cloud Run Admin API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;enable the &lt;a href="https://console.cloud.google.com/apis/api/artifactregistry.googleapis.com/overview"&gt;Artifact Registry API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://console.cloud.google.com/artifacts/create-repo"&gt;create a Repository in the Artifact Registry&lt;/a&gt; (Format: Docker, Region: your choice)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the build is green, you should see a new service in the &lt;a href="https://cloud.google.com/run/"&gt;Cloud Run&lt;/a&gt; console. When you click on the service, at the top you see the new public URL (ie. &lt;code&gt;app-name-123.run.app&lt;/code&gt;) for your service. Link a domain to that URL and 🎉, your API is now live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Let's recap a everyday workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer picks an issue and works on this amazing new feature for your product&lt;/li&gt;
&lt;li&gt;Developer pushes changes to a feature branch and opens a &lt;a href="https://github.blog/2019-02-14-introducing-draft-pull-requests/"&gt;Draft Pull Request&lt;/a&gt;. A Pull Request Preview is automatically deployed via the &lt;a href="https://github.com/apps/google-cloud-build"&gt;GitHub/Google Cloud Build&lt;/a&gt; integration and changes can be tested&lt;/li&gt;
&lt;li&gt;Developer marks the Pull Request as "Ready for Review"&lt;/li&gt;
&lt;li&gt;Team reviews the PR, discusses the changes and approves (hopefully ;))&lt;/li&gt;
&lt;li&gt;Pull Request is merged into &lt;code&gt;main&lt;/code&gt; by the developer or reviewer&lt;/li&gt;
&lt;li&gt;GitHub &lt;code&gt;publish.yml&lt;/code&gt; Action is triggered ("push to main") and runs tests, lint etc.&lt;/li&gt;
&lt;li&gt;If everything goes well, &lt;code&gt;npx semantic-release&lt;/code&gt; executes, calculating a new version number, summarizing the release notes and creates a new tag and release&lt;/li&gt;
&lt;li&gt;Google Cloud Build trigger is fired on "new tag", builds your app and pushes the Docker image to the Artifact Registry&lt;/li&gt;
&lt;li&gt;Cloud Build starts the Cloud Run deployment with the new image (which has the version number)&lt;/li&gt;
&lt;li&gt;Cloud Run starts the new service and changes the traffic allocation to the new instance&lt;/li&gt;
&lt;li&gt;The change is live, start over with the next feature 😎&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud Run enables you to split traffic between multiple revisions, so you can perform gradual rollouts such as canary deployments or blue/green deployments. Combined with automatic rollbacks, you can skip your staging environment alltogether and continously deploy to production. Developers can easily revert changes by merging a commit to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading! If you have any questions or comments, please &lt;a href="https://twitter.com/zenteredco"&gt;reach out on Twitter&lt;/a&gt; or &lt;a href="https://github.com/zentered/zentered/discussions/new?category=ideas-feedback"&gt;start a discussion on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>googlecloud</category>
      <category>continuousdeployment</category>
      <category>semanticversioning</category>
    </item>
    <item>
      <title>Universal/Isomorphic Web Apps on Google Cloud Run</title>
      <dc:creator>Patrick</dc:creator>
      <pubDate>Mon, 28 Sep 2020 11:55:43 +0000</pubDate>
      <link>https://dev.to/zentered/universal-isomorphic-web-apps-on-google-cloud-run-57hd</link>
      <guid>https://dev.to/zentered/universal-isomorphic-web-apps-on-google-cloud-run-57hd</guid>
      <description>&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;p&gt;In this article we'll learn how to launch a JavaScript application on Google Cloud Run (fully managed) with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server Side Rendering (SSR)&lt;/li&gt;
&lt;li&gt;TLS (managed by Google)&lt;/li&gt;
&lt;li&gt;Global Content Delivey Network (CDN)&lt;/li&gt;
&lt;li&gt;resources and services in the same network (no added network delays)&lt;/li&gt;
&lt;li&gt;No cold starts*&lt;/li&gt;
&lt;li&gt;No CORS (avoid preflight requests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll also demonstrate how to get close to &lt;a href="https://12factor.net/dev-prod-parity"&gt;dev/prod parity&lt;/a&gt; with a local development setup.&lt;/p&gt;

&lt;p&gt;You can find all relevant configuration files and code example the &lt;a href="https://github.com/zentered/universal-apps-on-cloud-run"&gt;Universal Apps on Cloud Run GitHub Repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The concept of universal/isomorphic apps is that the first page is rendered on the server and delivered to the client in plain HTML and CSS, while additional JavaScript is delivered after, to allow the "application like" usability known from Single Page Applications. By caching the rendered pages on the CDN, we aim for fast initial page load with low First Input Delay/Largest Contentful Paint (&lt;a href="https://web.dev/vitals/"&gt;Web Vitals&lt;/a&gt;). By avoiding CORS preflight requests, we skip the additional &lt;code&gt;OPTIONS&lt;/code&gt; request to the API which usually adds additional delay to each ajax request.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article requires basic knowledge of N*xt.js and Node.js as we'll be building on top of that.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(*) The &lt;code&gt;min-instances&lt;/code&gt; setting is currently in Alpha and should be available in Beta soon, which allows to keep a certain number of instances running.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Single Page Applications (SPA) are easy for developers and great for many things, but when it comes to web performance and search/SEO scores, Server Side Rendered (SSR) applications still perform much better.&lt;/p&gt;

&lt;p&gt;For a recent project, we looked into Cloud Run as an easy-to-use, scalable infrastructure. We chose &lt;a href="https://nuxtjs.org"&gt;Nuxt.js&lt;/a&gt; and &lt;a href="https://vuejs.org"&gt;Vue.js&lt;/a&gt; for simplicty over &lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt; and &lt;a href="https://reactjs.org"&gt;React.js&lt;/a&gt;. Data is delivered by a &lt;a href="https://nodejs.org/"&gt;Node.js&lt;/a&gt; API. Next.js with SSR requires a build step and a web server, while the API also requires a separate environment. In this article we call them &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;api&lt;/code&gt; services.&lt;/p&gt;

&lt;p&gt;To achieve a fully automated deployment pipeline we use &lt;a href="https://cloud.google.com/cloud-build"&gt;Google Cloud Build&lt;/a&gt; and &lt;a href="https://semantic-release.gitbook.io/semantic-release/"&gt;Semantic Release&lt;/a&gt; to version and build Docker images based on our code on &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/run"&gt;Google Cloud Run&lt;/a&gt; is an easy and reliable infrastructure for running Docker containers and they recently added Load Balancer and CDN support for the managed service, which means there is zero devops overhead on Kubernetes (k8s); everything is managed by Google. For advanced configurations they still offer &lt;a href="https://cloud.google.com/anthos/run"&gt;Cloud Run for Anthos&lt;/a&gt; to screw and tweak with a custom Kubernetes config, but we wanted to focus on product development rather than infrastructure, and Cloud Run (managed) makes that possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Universal Web App with N*xt
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Nuxt.js
&lt;/h4&gt;

&lt;p&gt;Set up your N*xt App as usual with a generator or boilerplate. For this article we used create-nuxt-app with Universal mode and &lt;a href="https://github.com/axios/axios"&gt;axios&lt;/a&gt;(a Promise based HTTP client) support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-nuxt-app web
yarn create nuxt-app web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our example we want to retrieve a travel itinerary from our API by using an axios request like &lt;code&gt;$axios.get('/travels')&lt;/code&gt; and add images or other assets with &lt;code&gt;/assets/yourimage.png&lt;/code&gt;. We'll add the prefix &lt;code&gt;/api&lt;/code&gt; later in our config.&lt;/p&gt;

&lt;p&gt;Usually you would need to set up a &lt;a href="https://axios.nuxtjs.org/options.html#proxy"&gt;proxy in axios&lt;/a&gt; to rewrite &lt;code&gt;/api/&lt;/code&gt; and &lt;code&gt;/assets&lt;/code&gt; to the correct URLs, but this will be handled by Google Cloud's Load Balancer, so there is no need. Instead, we set the &lt;a href="https://axios.nuxtjs.org/options.html#browserbaseurl"&gt;environment variables&lt;/a&gt; &lt;code&gt;API_URL&lt;/code&gt; and &lt;code&gt;API_URL_BROWSER&lt;/code&gt; in our local Docker Compose setup to overwrite the axios configuration. These are set in &lt;code&gt;next.config.js&lt;/code&gt; to avoid issues with the live version on Google Cloud.&lt;/p&gt;

&lt;p&gt;For the local setup to work, &lt;code&gt;BASE_URL&lt;/code&gt; and &lt;code&gt;API_URL_BROWSER&lt;/code&gt; are set to the nginx proxy, while &lt;code&gt;API_URL&lt;/code&gt; is used for the internal SSR requests from Nuxt directly to the API service. On GCP (Google Cloud Platform) these adjustments are not needed.&lt;/p&gt;

&lt;p&gt;At this point, the web app isn't working, because the api and assets are not reachable by Next when running &lt;code&gt;npm start&lt;/code&gt;, so we'll move on to the other pieces and get back to this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js API with Fastify
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.fastify.io"&gt;Fastify&lt;/a&gt; is an amazing Node.js framework for API development. It works very similar to &lt;a href="http://expressjs.com"&gt;Express&lt;/a&gt;. It's built for speed and has some great convenience functions built in, such as automatic &lt;a href="https://swagger.io/specification/"&gt;OpenAPI/Swagger&lt;/a&gt; docs generation, input and output schemas and validation and a great plugin system. Here's a basic Node.js server set up with fastify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AutoLoad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify-autoload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;disableRequestLogging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AutoLoad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify-http-proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;upstream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this demo, we are using &lt;code&gt;/routes/travels/list.js&lt;/code&gt; to automatically generate an endpoint &lt;code&gt;GET /travels&lt;/code&gt; and deliver some travel data. These are locations we'll be travelling to in the coming months of 2020, so if you're nearby, &lt;a href="https://twitter.com/zenteredco"&gt;give us a shout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two things which are important here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;disableRequestLogging&lt;/code&gt; - Google Cloud Run does that already, so there's no need to log requests in Fastify&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fastify-http-proxy&lt;/code&gt; - this is a little tricky. In our Docker Compose environment, all internal requests (requests posted by next directly to the API for server-side rendering) still have &lt;code&gt;/api/&lt;/code&gt; in their path, so we need to proxy &lt;code&gt;/api/travels&lt;/code&gt; to &lt;code&gt;/travels&lt;/code&gt; with this little hack. For external requests, our nginx server and Google Load Balancer rewrite the path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This should be straightforward. Once your API delivers data on &lt;code&gt;localhost:3000/travels&lt;/code&gt;, let's move to deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Cloud
&lt;/h3&gt;

&lt;p&gt;Before we start with the deployment, you need to set up &lt;code&gt;gcloud&lt;/code&gt; and create a project on Google Cloud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;gcloud&lt;/code&gt; CLI &lt;a href="https://cloud.google.com/sdk/gcloud"&gt;https://cloud.google.com/sdk/gcloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project on &lt;a href="https://console.cloud.google.com/"&gt;https://console.cloud.google.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set the default project: &lt;code&gt;gcloud config set project universal-apps-cloud-run-demo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll need the following services activated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Build API (&lt;a href="https://console.cloud.google.com/marketplace/product/google/cloudbuild.googleapis.com"&gt;https://console.cloud.google.com/marketplace/product/google/cloudbuild.googleapis.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Cloud Run API (&lt;a href="https://console.cloud.google.com/run/create"&gt;https://console.cloud.google.com/run/create&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Network Services / Load Balancing (&lt;a href="https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list"&gt;https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Container Registry (&lt;a href="https://console.cloud.google.com/gcr/images/"&gt;https://console.cloud.google.com/gcr/images/&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Deployment Pipeline
&lt;/h4&gt;

&lt;p&gt;We'd recommend to use one repository for the api and one for the web service, but for this demo we put both services in one. Once you have the repos set up and pushed the code, go to the GitHub Marketplace and install the &lt;a href="https://github.com/marketplace/google-cloud-build"&gt;Google Cloud Build&lt;/a&gt; App. Add the repositories to the integration and connect the GCP projects. When you separate into two projects, don't forget to change the &lt;code&gt;cloudbuild.yaml&lt;/code&gt; in both projects to build from root, instead of a folder (&lt;code&gt;- web/.&lt;/code&gt; and &lt;code&gt;- api/.&lt;/code&gt; to &lt;code&gt;.&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;In this article, we skip the Semantic Release setup, you can read and adjust this from a previous article &lt;a href="///articles/serverless-v2.0.0/"&gt;Serverless 2.0.0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We create two triggers for deployment: one for web and one for api.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4eoHM4xi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/build-trigger-api.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4eoHM4xi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/build-trigger-api.png" alt="build trigger for web" width="880" height="1059"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C4Sdb0T---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/build-trigger-web.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C4Sdb0T---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/build-trigger-web.png" alt="build trigger for" width="880" height="1059"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You also need to give permissions to Cloud Build to deploy on Cloud Run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WW4JoUsX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/cloud-build-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WW4JoUsX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/cloud-build-settings.png" alt="coud build settings" width="880" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Web requires a substitition variable &lt;code&gt;_API_URL&lt;/code&gt; set to the domain you want to run the service on. We're using &lt;code&gt;https://demo.zentered.io/api&lt;/code&gt; here. A substition variable is an environment variable that you set during build time. You can deploy the same code with various API_URLs to test new releases for example.&lt;/p&gt;

&lt;p&gt;It's important to note that the build on Cloud Build happens with &lt;code&gt;NODE_ENV=production&lt;/code&gt;, this means your &lt;code&gt;devDependencies&lt;/code&gt; are not installed. Make sure you have all build-dependencies in the &lt;code&gt;dependencies&lt;/code&gt; of your package.json.&lt;/p&gt;

&lt;p&gt;Once this is done, you can push to your main branch and watch Cloud Build deploy your services to Cloud Run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CDYEaLrQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/cloud-build-successful.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CDYEaLrQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/cloud-build-successful.png" alt="cloud build works" width="880" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you work with Semantic Release, a GitHub Action can create new Tags/Releases for you. Modify the trigger to build releases only.&lt;/p&gt;
&lt;h4&gt;
  
  
  Services on Google Cloud Run
&lt;/h4&gt;

&lt;p&gt;Google Cloud Run (GCR) is a fully managed compute platform for deploying and scaling containerized applications quickly and securely. You can focus on your application, wrap them in a Docker container and let GCR do the rest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AUFkyYCr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/cloud-run-successful-deployment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AUFkyYCr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/cloud-run-successful-deployment.png" alt="cloud run overview" width="880" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the service details you'll get a URL for both services which ends with &lt;em&gt;.run.app&lt;/em&gt;. You should be able to access both services, but Nuxt will not be able to retrieve data from assets or the API yet.&lt;/p&gt;
&lt;h4&gt;
  
  
  Assets Bucket
&lt;/h4&gt;

&lt;p&gt;To store images and other assets for your site, head over to Google Cloud Storage, create a public bucket and upload some files. At a later stage you might want to have a build step to copy assets from your web service to Cloud Storage.&lt;/p&gt;
&lt;h4&gt;
  
  
  Load Balancer with SSL and CDN
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Quick recap.&lt;/em&gt; We have a deployment pipeline to deploy two services, web and api, automatically to Cloud Run. Pusing to the repo triggers a deployment. Both services are public and have their internal &lt;code&gt;*.run.app&lt;/code&gt; URLs. We also have a public bucket for assets etc.&lt;/p&gt;

&lt;p&gt;Now we're placing the last piece of the puzzle to make everything work together: the &lt;a href="https://cloud.google.com/load-balancing/docs/negs/setting-up-serverless-negs"&gt;Load Balancer for Serverless Network Endpoint Groups (NEGs)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will need a domain and an A record to an external IP address from Google Cloud that you can create as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute addresses create web &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ip-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;IPV4 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the IP with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute addresses describe web &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"get(address)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to replace &lt;code&gt;europe-west1&lt;/code&gt; with any of the following regions that is closer to you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asia-east1 (Taiwan)&lt;/li&gt;
&lt;li&gt;asia-northeast1 (Tokyo)&lt;/li&gt;
&lt;li&gt;asia-northeast2 (Osaka)&lt;/li&gt;
&lt;li&gt;europe-north1 (Finland)&lt;/li&gt;
&lt;li&gt;europe-west1 (Belgium)&lt;/li&gt;
&lt;li&gt;europe-west4 (Netherlands)&lt;/li&gt;
&lt;li&gt;us-central1 (Iowa)&lt;/li&gt;
&lt;li&gt;us-east1 (South Carolina)&lt;/li&gt;
&lt;li&gt;us-east4 (Northern Virginia)&lt;/li&gt;
&lt;li&gt;us-west1 (Oregon)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See &lt;a href="https://cloud.google.com/run/docs/locations"&gt;Cloud Run locations&lt;/a&gt; for a full list and pricing.&lt;/p&gt;

&lt;p&gt;If you need more information about the individual parts, you can head over to the &lt;a href="https://cloud.google.com/load-balancing/docs/negs/setting-up-serverless-negs"&gt;Google Tutorial&lt;/a&gt;. Here's a summary of the commands that need to be executed in order:&lt;/p&gt;

&lt;h6&gt;
  
  
  Network Endpoint Group (NEG)
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute network-endpoint-groups create web-neg &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1 &lt;span class="nt"&gt;--network-endpoint-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SERVERLESS &lt;span class="nt"&gt;--cloud-run-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web
gcloud beta compute network-endpoint-groups create api-neg &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1 &lt;span class="nt"&gt;--network-endpoint-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SERVERLESS &lt;span class="nt"&gt;--cloud-run-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Backend Services
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute backend-buckets create assets &lt;span class="nt"&gt;--gcs-bucket-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uwa-demo-bucket &lt;span class="nt"&gt;--enable-cdn&lt;/span&gt;
gcloud beta compute backend-services create web-service &lt;span class="nt"&gt;--global&lt;/span&gt;
gcloud beta compute backend-services add-backend web-service &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--network-endpoint-group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web-neg &lt;span class="nt"&gt;--network-endpoint-group-region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1
gcloud beta compute backend-services create api-service &lt;span class="nt"&gt;--global&lt;/span&gt;
gcloud beta compute backend-services add-backend api-service &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--network-endpoint-group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;api-neg &lt;span class="nt"&gt;--network-endpoint-group-region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  URL Maps (🧙‍♀)
&lt;/h6&gt;

&lt;p&gt;This is where the magic happens. Create a file &lt;code&gt;url-map.yaml&lt;/code&gt; with this content. Replace &lt;code&gt;universal-apps-cloud-run-demo&lt;/code&gt; with your project id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;defaultService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.googleapis.com/compute/v1/projects/universal-apps-cloud-run-demo/global/backendServices/web-service&lt;/span&gt;
&lt;span class="na"&gt;hostRules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
    &lt;span class="na"&gt;pathMatcher&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path-matcher-1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compute#urlMap&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
&lt;span class="na"&gt;pathMatchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;defaultService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.googleapis.com/compute/v1/projects/universal-apps-cloud-run-demo/global/backendServices/web-service&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path-matcher-1&lt;/span&gt;
    &lt;span class="na"&gt;pathRules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/api/&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/api/*&lt;/span&gt;
        &lt;span class="na"&gt;routeAction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;urlRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;pathPrefixRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.googleapis.com/compute/v1/projects/universal-apps-cloud-run-demo/global/backendServices/api-service&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/assets/*&lt;/span&gt;
        &lt;span class="na"&gt;routeAction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;urlRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;pathPrefixRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.googleapis.com/compute/v1/projects/universal-apps-cloud-run-demo/global/backendBuckets/assets&lt;/span&gt;
&lt;span class="na"&gt;selfLink&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.googleapis.com/compute/v1/projects/universal-apps-cloud-run-demo/global/urlMaps/web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then import the URL map on GCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute url-maps import web &lt;span class="nt"&gt;--source&lt;/span&gt; url-map.yaml &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create two url rewrite rules for the load balancer, to route all requests for &lt;code&gt;/api&lt;/code&gt; to the api service and all requests to &lt;code&gt;/assets&lt;/code&gt; to the storage bucket.&lt;/p&gt;

&lt;h6&gt;
  
  
  SSL certs
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute ssl-certificates create web-ssl-cert &lt;span class="nt"&gt;--domains&lt;/span&gt; demo.zentered.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  HTTPS Proxy
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute target-https-proxies create web-https-proxy &lt;span class="nt"&gt;--ssl-certificates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web-ssl-cert &lt;span class="nt"&gt;--url-map&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Forwarding Rules
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute forwarding-rules create https-web-rule &lt;span class="nt"&gt;--address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web &lt;span class="nt"&gt;--target-https-proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web-https-proxy &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Enable CDN
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute backend-services update web-service &lt;span class="nt"&gt;--enable-cdn&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt;
gcloud beta compute backend-services update api-service &lt;span class="nt"&gt;--enable-cdn&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Tada
&lt;/h6&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nZQlnrnO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/uwa-page-load.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nZQlnrnO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/uwa-page-load.png" alt="successful page load" width="880" height="1102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can verify your Load Balancer config here: &lt;a href="https://console.cloud.google.com/net-services/loadbalancing/details/http/web"&gt;https://console.cloud.google.com/net-services/loadbalancing/details/http/web&lt;/a&gt;. It should show the two backend services, the assets bucket and the IP with SSL on the Frontend. In the Cloud CDN tab, all three backends should be listed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UFfhM-sp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/google-load-balancer-verification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UFfhM-sp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zentered.co/images/universal-app-cloud-run/google-load-balancer-verification.png" alt="load balancer verification" width="880" height="1059"&gt;&lt;/a&gt;&lt;br&gt;
After a few minutes, your SSL certificate should be ready and your website should show. It might take a few minutes, you can check the status with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssl-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  GCP Cleanup
&lt;/h6&gt;

&lt;p&gt;To roll back / remove all the resources created before, execute the following steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta compute forwarding-rules delete https-web-rule &lt;span class="nt"&gt;--global&lt;/span&gt;
gcloud beta compute target-https-proxies delete web-https-proxy
gcloud beta compute url-maps delete web
gcloud beta compute backend-services delete web-service &lt;span class="nt"&gt;--global&lt;/span&gt;
gcloud beta compute network-endpoint-groups delete web-neg &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1
gcloud beta compute backend-services delete api-service &lt;span class="nt"&gt;--global&lt;/span&gt;
gcloud beta compute network-endpoint-groups delete api-neg &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1
gcloud compute addresses delete web &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running locally with Docker Compose
&lt;/h3&gt;

&lt;p&gt;Now as everything is running in Production, we'd like to achieve a similar setup to develop efficiently on our application and API. For SSL and path rewrites (/api and /assets), we use nginx:&lt;/p&gt;

&lt;p&gt;Head over to &lt;a href="https://github.com/FiloSottile/mkcert"&gt;https://github.com/FiloSottile/mkcert&lt;/a&gt; and download/install &lt;code&gt;mkcert&lt;/code&gt;, this is required to generate SSL certificates for localhost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;certs
mkcert localhost 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the entire nginx config with both upstreams (web and api), the path rewrite rules for api and assets, and the SSL configuration:&lt;/p&gt;

&lt;p&gt;Last but not least, you'll need Docker Compose to run all services together. Entrypoint for the application is &lt;code&gt;https://localhost:8080&lt;/code&gt; which loads your N*xt app with SSL and redirects all requests to &lt;code&gt;/api/&lt;/code&gt; to the api service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;api service: port 3000&lt;/li&gt;
&lt;li&gt;web service: port 5000&lt;/li&gt;
&lt;li&gt;nginx ssl proxy: port 8080&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;You need to explicitly add &lt;code&gt;https://&lt;/code&gt; in the browser, as there's no http-to-https redirect yet&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We use environment variables to &lt;a href="https://axios.nuxtjs.org/options.html#baseurl"&gt;overwrite Axios behaviour&lt;/a&gt;. Internal requests for server-side rendering are sent to &lt;code&gt;http://api:3000/api&lt;/code&gt;, while client-side requests to to &lt;code&gt;https://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;This article shows how to run universal apps on Google Cloud Run (fully managed) with very little operations overhead. There's no need to SSH into machines or take care about regular system updates, and the system can easily scale by changing the &lt;code&gt;max-instances&lt;/code&gt; setting in the configuration or by deploying the same services in new regions.&lt;/p&gt;

&lt;p&gt;You can clone/download all parts of this tutorial from our &lt;a href="https://github.com/zentered/universal-apps-on-cloud-run"&gt;GitHub Repo&lt;/a&gt;. Please &lt;a href="https://github.com/zentered/universal-apps-on-cloud-run/issues/new"&gt;open an issue&lt;/a&gt; if you find anything that is not working in this tutorial or &lt;a href="https://twitter.com/zenteredco"&gt;reach out to us on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/load-balancing/docs/negs/setting-up-serverless-negs"&gt;https://cloud.google.com/load-balancing/docs/negs/setting-up-serverless-negs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/load-balancing/docs/url-map?hl=en_US"&gt;https://cloud.google.com/load-balancing/docs/url-map?hl=en_US&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.smashingmagazine.com/2020/05/getting-started-axios-nuxt/"&gt;https://www.smashingmagazine.com/2020/05/getting-started-axios-nuxt/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Special Thanks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/steren"&gt;Steren&lt;/a&gt; from the Google Cloud Run team&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/romsters"&gt;Roman&lt;/a&gt; for helping with the url map&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/pp1987"&gt;Patrick&lt;/a&gt;, &lt;a href="https://twitter.com/thetwopct"&gt;James&lt;/a&gt;, &lt;a href="https://twitter.com/one_desert"&gt;Yann&lt;/a&gt; and &lt;a href="https://twitter.com/jonasbroms"&gt;Jonas&lt;/a&gt; for reviewing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>googlecloud</category>
      <category>vue</category>
      <category>node</category>
    </item>
  </channel>
</rss>
