<?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: Antonio</title>
    <description>The latest articles on DEV Community by Antonio (@uf4no).</description>
    <link>https://dev.to/uf4no</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%2Fuser%2Fprofile_image%2F105535%2Ff0e24dfa-74e1-4081-882a-5f099f1643bd.jpg</url>
      <title>DEV Community: Antonio</title>
      <link>https://dev.to/uf4no</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uf4no"/>
    <language>en</language>
    <item>
      <title>Easily send emails in Strapi with any provider</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Tue, 01 Dec 2020 10:22:22 +0000</pubDate>
      <link>https://dev.to/uf4no/easily-send-emails-in-strapi-with-any-provider-2oka</link>
      <guid>https://dev.to/uf4no/easily-send-emails-in-strapi-with-any-provider-2oka</guid>
      <description>&lt;p&gt;Sending emails is a fundamental feature in most projects. From welcoming new users to sending notifications and reminders, it's something that I find myself doing in every project.&lt;/p&gt;

&lt;p&gt;I've been using &lt;a href="https://strapi.io" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; for a few months now as it allows me to &lt;a href="https://antonioufano.com/articles/building-apis-fast-with-strapi-an-overview-34" rel="noopener noreferrer"&gt;create APIs easily and fast&lt;/a&gt; and, even though it &lt;strong&gt;includes a default plugin to send emails out the box, it's not the best option to use in Production&lt;/strong&gt;. There are multiple plugins for different email providers but in this article, I'll show you how to &lt;strong&gt;use a plugin that works with any provider&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you shouldn't use Strapi's default email plugin
&lt;/h2&gt;

&lt;p&gt;Why do we need to install a new plugin if Strapi has one out of the box? As they mention in &lt;a href="https://strapi.io/documentation/v3.x/plugins/email.html#programmatic-usage" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt;, Strapi uses &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Sendmail" rel="noopener noreferrer"&gt;sendmail&lt;/a&gt;&lt;/strong&gt; as a default provider, which basically means that &lt;strong&gt;the emails are sent directly from the local server running Strapi&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You might use this during development but in Production it can cause you some issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's not 100% reliable&lt;/li&gt;
&lt;li&gt;Common mail servers, like &lt;strong&gt;Gmail, Outlook and Yahoo will block your emails if they are not being sent from a trusted server&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Your server might not have sendmail installed&lt;/li&gt;
&lt;li&gt;Sendmail won't probably work if you are deploying your Strapi application with Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To avoid these issues, it's better to use an external provider. In the Strapi documentation they have an example using Sendgrid with its own specific plugin. Although it's a great platform, Sendgrid's free tier is very limited (only 100 emails/day) so I prefer to use &lt;a href="https://www.sendinblue.com/" rel="noopener noreferrer"&gt;SendinBlue&lt;/a&gt; (free 300 emails/day) or for more email demanding projects, &lt;a href="https://elasticemail.com/" rel="noopener noreferrer"&gt;ElasticEmail&lt;/a&gt; ($0.09 per 1000 emails).&lt;/p&gt;

&lt;p&gt;Although there is &lt;a href="https://www.npmjs.com/package/strapi-provider-email-sendinblue" rel="noopener noreferrer"&gt;a specific plugin for SendinBlue&lt;/a&gt;, I don't like the idea of my application depending on one specific third party like that and I'm not sure if I'll be using the same provider forever, so &lt;strong&gt;I prefer to use a plugin that is provider agnostic&lt;/strong&gt;. And what do most email providers have in common? They all support the &lt;strong&gt;SMTP protocol&lt;/strong&gt;, so that's what we'll use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For this article, I'd assume we'll be using SendinBlue so go ahead and &lt;a href="https://app.sendinblue.com/account/register" rel="noopener noreferrer"&gt;create a free account&lt;/a&gt;. You can find the SMTP details in your account settings &amp;gt; SMTP &amp;amp; API.&lt;/p&gt;
&lt;/blockquote&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%2Fantonioufano.com%2Fimage_uploads%2F1606476235503.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%2Fantonioufano.com%2Fimage_uploads%2F1606476235503.png" alt="Sendinblue SMTP connection details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugin install and setup
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The first thing you'll need is a scaffolded Strapi project. You can check how to create one in &lt;a href="https://antonioufano.com/articles/building-apis-fast-with-strapi-an-overview-34" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The plugin we're going to use is &lt;a href="https://www.npmjs.com/package/strapi-provider-email-nodemailer" rel="noopener noreferrer"&gt;strapi-provider-email-nodemailer&lt;/a&gt; and, as I mentioned I choose this one for multiple reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's provider agnostic&lt;/li&gt;
&lt;li&gt;Uses SMTP protocol&lt;/li&gt;
&lt;li&gt;Super easy to configure and use&lt;/li&gt;
&lt;li&gt;Migrating to a different provider is super simple&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To install it, run &lt;code&gt;npm i strapi-provider-email-nodemailer&lt;/code&gt;. To configure it I recommend to use an .env file as your server details will probably be different in DEV and PROD. To checkout how to use .env files in Strapi, check &lt;a href="https://strapi.io/documentation/v3.x/concepts/configurations.html#configuration-using-environment-variables" rel="noopener noreferrer"&gt;this section of the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our .env file we'll add the following variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EMAIL_PROVIDER=nodemailer
EMAIL_SMTP_HOST=smtp.zzzz.zzzzz
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=xxXXXXxxxxXXX
EMAIL_SMTP_PASS=yyyYYYYYyyyYYYY
EMAIL_ADDRESS_FROM=aaaaaa@bbbbbb.com
EMAIL_ADDRESS_REPLY=ccccccc@dddddd.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Sendinblue's SMTP host is &lt;code&gt;smtp-relay.sendinblue.com&lt;/code&gt; and port is 587.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next step is to configure the plugin in the &lt;em&gt;config/plugins.js&lt;/em&gt; file:&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="c1"&gt;// File: config/plugins.js&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="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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_PROVIDER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;providerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_SMTP_HOST&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;smtp.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_SMTP_PORT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_SMTP_USER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_SMTP_PASS&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;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;defaultFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_ADDRESS_FROM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;defaultReplyTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EMAIL_ADDRESS_REPLY&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;span class="c1"&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, the configuration is pretty straight forward and we just have to assign the server and authentication details that we included in the env before.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't want to use an env file, you can populate these variables using &lt;code&gt;process.env.YOUR_ENV_VARIABLE&lt;/code&gt; instead of &lt;code&gt;env('YOUR_ENV_VARIABLE')&lt;/code&gt; or, if you're testing locally, directly assigning the values but &lt;strong&gt;remember not to commit these to your repository&lt;/strong&gt; for security reasons.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sending emails
&lt;/h2&gt;

&lt;p&gt;To make this example as simple as possible, I'll create an endpoint in our API that automatically sends an email. We'll create the following files inside &lt;em&gt;api&lt;/em&gt; folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/api/email/config/routes.json
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/api/email/config/routes.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/emails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"handler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email.send"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new POST endpoint &lt;em&gt;/emails&lt;/em&gt; in our API, and the requests will be handled by the &lt;em&gt;send&lt;/em&gt; method inside the Email controller, which we will create next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/api/email/controllers/Email.js
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// File /api/email/controllers/Email.js&lt;/span&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="cm"&gt;/**
 * Read the documentation () to implement custom controller functions
 */&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="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Sends an email to the recipient in the body of the request
   */&lt;/span&gt;
  &lt;span class="na"&gt;send&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;ctx&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendTo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="nx"&gt;strapi&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="s2"&gt;`Trying to send an email to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sendTo&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;try&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;emailOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sendTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Welcome!&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;This is a test HTML email.&amp;lt;/p&amp;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;await&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;strapi&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="s2"&gt;`Email sent to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sendTo&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email sent&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;strapi&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;`Error sending email to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sendTo&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error sending email&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;send&lt;/em&gt; method receives a POST request with the email in the body. We create an &lt;em&gt;emailOptions&lt;/em&gt; object that contains the properties for destination address (to), subject and html as the body of the email. Then we just have to use the the &lt;em&gt;strapi.plugins['email'].services.email.send&lt;/em&gt; method and pass it the &lt;em&gt;emailOptions&lt;/em&gt; object.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you need to add attachements, bcc or other email options, you can check the &lt;a href="https://nodemailer.com/message/" rel="noopener noreferrer"&gt;nodemailer message configuration here&lt;/a&gt;. You'd just need to add the options you need in the emailOptions object 😉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now if we start our Strapi application with &lt;code&gt;npm run develop&lt;/code&gt; and send a POST request to &lt;em&gt;/emails&lt;/em&gt; with Postman or cURL, our email will be sent 📬&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="c"&gt;# Send POST request to /emails via cURL&lt;/span&gt;

curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="s1"&gt;'localhost:1337/emails'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{"email": "example@email.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This is a very basic example to show you &lt;strong&gt;how to programmatically send emails with Strapi via SMTP&lt;/strong&gt;. In my case I normally trigger emails in cron jobs or in an authentication protected endpoints after users do a certain action.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: You shouldn't have a public endpoint in your API that triggers emails as it can be easily exploited and you could be charged by your email provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I like using nodemailer because it's &lt;strong&gt;provider agnostic and it allows me to easily change providers&lt;/strong&gt; by just changing the server and credential details in my env file. In addition, it supports Amazon SES transport so you can use that option. You can find more info about &lt;a href="https://nodemailer.com/transports/ses/" rel="noopener noreferrer"&gt;how to use nodemailer with SES here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you liked this article, you can &lt;a href="https://twitter.com/uf4no" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt; where I share dev tips, interesting articles and updates about the progress of my projects 🤙&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com/blog" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; where you can find other articles about web development focused on Node.js, Vue, Laravel and anything related with product development.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Use Vuexfire to simplify your application state management</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Wed, 18 Nov 2020 13:09:27 +0000</pubDate>
      <link>https://dev.to/uf4no/use-vuexfire-to-simplify-your-application-state-management-3p8n</link>
      <guid>https://dev.to/uf4no/use-vuexfire-to-simplify-your-application-state-management-3p8n</guid>
      <description>&lt;p&gt;The problem tackled by VuexFire is pretty simple: &lt;strong&gt;keeping you state data in sync with the data in Firebase Firestore or RTDB&lt;/strong&gt;. If you're familiar with Vue and Vuex, I'm sure you've faced this problem before. Let's think on a simple app like a to-do list.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every time a user creates a new to-do, a Vuex action sends a request to the backend and commits a mutation to update the application state.&lt;/li&gt;
&lt;li&gt;When the user completes a to-do, a Vuex action sends a request to the backend and then commits another mutation to update that particular to-do in the state.&lt;/li&gt;
&lt;li&gt;When a user deletes a to-do, a Vuex action sends another request to the backend and then commits another mutation to delete that particular to-do from the state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, &lt;strong&gt;every user interaction requires a Vuex action and a mutation&lt;/strong&gt; to keep the application in sync with the data in the backend. &lt;strong&gt;VuexFire simplies this for us a lot, by binding our application state to Firestore or RTDB and updating the state automatically without the need to write or commit any mutation.&lt;/strong&gt; It archieves this by providing its own set of mutations that take care of common things like adding or deleting items from an array or updating an object.&lt;/p&gt;

&lt;p&gt;Let me walk you through a basic example.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm assuming we already have a Vue CLI project scaffolded with Vuex installed. You can find more info on how to do that &lt;a href="https://cli.vuejs.org/guide/prototyping.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Retrieving data with Vuexfire
&lt;/h3&gt;

&lt;p&gt;Let's say we want to build an app to manage books. The first thing I'd need to do is to install the Firebase and Vuexfire dependencies in the Vue project running &lt;code&gt;npm i firebase vuexfire&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I created a &lt;em&gt;firebase.js&lt;/em&gt; file in which I'll configure the Firebase SDK with the project keys and, assuming that our books will be stored in a Firebase collection named &lt;em&gt;books&lt;/em&gt;, create a reference to it. It'll look like this:&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="c1"&gt;// File src/firebase.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;firebase&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;firebase/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firebase/firestore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_APIKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_AUTHDOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;databaseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_DATABASEURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_PROJECTID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storageBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_STORAGEBUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messagingSenderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_MESSAGINGSENDERID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_APPID&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebaseApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// collection reference&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;booksCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// exports collection reference to use it in the Vuex Store&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;booksCollection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this example I'm loading the Firebase keys from environment variables but you can load the values directly 😉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the application store, I'd want to keep all the books in a property named &lt;em&gt;allBooks&lt;/em&gt; inside the state, so I''ll just have to create it and initialise it to an empty array:&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="c1"&gt;// File src/store/index.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&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;vue&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;Vuex&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;vuex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vuex&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// list of all books initialized empty&lt;/span&gt;
    &lt;span class="na"&gt;allBooks&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="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="na"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="na"&gt;getters&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;The next thing I'd need to do is to create a Vuex action that binds the &lt;em&gt;allBooks&lt;/em&gt; property from the state with a Firestore query that returns the data from the &lt;em&gt;books&lt;/em&gt; collection:&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="c1"&gt;// File src/store/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&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;vue&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;Vuex&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;vuex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// imports collection reference from firebase.js file&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;booksCollection&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;@/firebase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// imports required to bind the state to Firebase using Vuexfire&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;firestoreAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vuexfireMutations&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;vuexfire&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vuex&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allBooks&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="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * @param context deconstructed to get only the method to create the ref
     */&lt;/span&gt;
    &lt;span class="na"&gt;bindBooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firestoreAction&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;bindFirestoreRef&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="c1"&gt;// return the promise returned by `bindFirestoreRef`&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bindFirestoreRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allBooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;booksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&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;asc&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;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// adds Vuexfire built-in mutations&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;vuexfireMutations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allBooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allBooks&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, the action &lt;em&gt;bindBooks&lt;/em&gt; is a firestoreAction, which I've imported from vuexfire. This action returns a promise resolved by &lt;em&gt;bindFirestoreRef()&lt;/em&gt;, which receives two parameters: the attribute in our state where we want to bind the data (in our case &lt;em&gt;allBooks&lt;/em&gt;), and the query that will return the data (in our case, all books ordered by the author).&lt;/p&gt;

&lt;p&gt;I also imported &lt;em&gt;vuexfireMutations&lt;/em&gt; and added them inside our mutations using the destructuring operator &lt;em&gt;...&lt;/em&gt;. Finally, I created a simple getter that returns the &lt;em&gt;allBooks&lt;/em&gt; list from the state, which I'll use in a component.&lt;/p&gt;

&lt;p&gt;The next step is to create a component to display all the books. A very simple one would be like this:&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="c1"&gt;// App.vue file&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;My&lt;/span&gt; &lt;span class="nx"&gt;Book&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book in allBooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book.id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Written&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;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;mapGetters&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;vuex&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;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;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;mounted&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;$store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bindBooks&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;computed&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="nx"&gt;mapGetters&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allBooks&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;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&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;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Avenir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Helvetica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sans&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;webkit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;smoothing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;antialiased&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;moz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;osx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;smoothing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;c3e50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;px&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="nx"&gt;book&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="nx"&gt;solid&lt;/span&gt; &lt;span class="nx"&gt;gray&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;lt;&lt;/span&gt;&lt;span class="sr"&gt;/style&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, on the &lt;em&gt;mounted&lt;/em&gt; lifecycle hook, I dispatch the action I created in the store, which will get the list of data from the Firestore &lt;em&gt;books&lt;/em&gt; collection and then all books will be displayed. That awesome because it didn't took too much code to build this, but &lt;strong&gt;the best part is that if we add, delete or update books directly from the Firebase console, our app will refresh its store automatically&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6aKnQGN4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://antonioufano.com/image_uploads/1605259669684.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6aKnQGN4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://antonioufano.com/image_uploads/1605259669684.gif" alt="Adding and removing books"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Writting to Firestore with Vuexfire
&lt;/h3&gt;

&lt;p&gt;Up until now, I've shown you how to use Vuexfire read and automatically keep the application state on sync with Firestore but in a real world app, we'd need to create, update or delete documents from our database. For that scenario, Vuexfire does not provide any methods or helpers and, &lt;a href="https://vuefire.vuejs.org/vuexfire/writing-data.html"&gt;as mentioned in the documentation&lt;/a&gt;, it recommends you to use the Firebase JS SDK.&lt;/p&gt;

&lt;p&gt;Two simple Vuex actions to add and delete books in the collection would look like this:&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="c1"&gt;// File: src/store/index.js&lt;/span&gt;

&lt;span class="c1"&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allBooks&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="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * @param context deconstructed to get only the method to create the ref
     */&lt;/span&gt;
    &lt;span class="na"&gt;bindBooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firestoreAction&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;bindFirestoreRef&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="c1"&gt;// return the promise returned by `bindFirestoreRef`&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bindFirestoreRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allBooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;booksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&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;asc&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;span class="na"&gt;addBook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firestoreAction&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="nx"&gt;payload&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="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Adding a new book!&lt;/span&gt;&lt;span class="dl"&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;booksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newBook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;deleteBook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firestoreAction&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="nx"&gt;payload&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="nx"&gt;console&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="s2"&gt;`Deleting book &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookId&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;return&lt;/span&gt; &lt;span class="nx"&gt;booksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;delete&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;blockquote&gt;
&lt;p&gt;Note: we'd need to pass the payload for each action from the component. You can see the detailed code in &lt;a href="https://github.com/uF4No/vuexfire-example/blob/main/src/App.vue"&gt;this file of the repo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice that in these actions, &lt;strong&gt;we're not commiting any mutations as we usually do in Vuex&lt;/strong&gt;.That's because the first action &lt;em&gt;bindBooks&lt;/em&gt; will take care of keeping the application state in sync with Firestore. So even though we have to actually trigger the &lt;em&gt;add()&lt;/em&gt; or &lt;em&gt;delete()&lt;/em&gt; functions, &lt;strong&gt;with Vuexfire we don't have to worry about refreshing our state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can find all the code of this article in &lt;a href="https://github.com/uF4No/vuexfire-example"&gt;the following repo in GitHub&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;As you can see, Vuexfire is a pretty nice tool you can use to simplify the state management of your Vue app. I've integrated it in &lt;a href="https://quicktalks.io"&gt;one of my latest projects&lt;/a&gt; and it has helped me to get rid of a ton of code and, the less code you have, the less bugs you'll probably find 😅&lt;/p&gt;

&lt;p&gt;Of course, it has its limitations, like for example, pagination. However thanks to some guidance I found on StackOverflow, I've been able to code a simple pagination that works for me. I'll share it with you in the next article.&lt;/p&gt;

&lt;p&gt;If you liked this article, you can &lt;a href="https://twitter.com/uf4no"&gt;follow me on Twitter&lt;/a&gt; where I share dev tips an interesting articles and updates about the progress of my projects 🤙&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com/blog"&gt;my blog&lt;/a&gt; where you can find other articles about web development focused on Node.js, Vue, Laravel and anything related with product development.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>vue</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Leassons learned using Firebase for the first time</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Mon, 09 Nov 2020 10:54:10 +0000</pubDate>
      <link>https://dev.to/uf4no/leassons-learned-using-firebase-for-the-first-time-3mib</link>
      <guid>https://dev.to/uf4no/leassons-learned-using-firebase-for-the-first-time-3mib</guid>
      <description>&lt;p&gt;A couple of weeks ago I decided that I wanted to build and launch a new project in a few days. To achieve that, I'd need to simplify as much as possible my tasks so I thought it was the perfect moment to learn Firebase and use it in a project for the first time. The project is still under development but so far I've learnt a few lessons I'd like to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should you use Firebase?
&lt;/h2&gt;

&lt;p&gt;The main reason I had to use Firebase was curiosity. I've been wanting to try it for a while now and with the launch of &lt;a href="https://aws.amazon.com/amplify/"&gt;AWS Amplify&lt;/a&gt; (which is pretty similar) my curiosity kicked in again. But other factors can make you decide to choose Firebase. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Free plan: The free tier is good enough to build and run a small side project. It will give you 50k document reads, 20k document writes 20k document deletes, 1GB of stored data and 10GB of network. &lt;a href="https://firebase.google.com/docs/firestore/quotas#free-quota"&gt;See free plan details&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fast developing experience: writing an app from scratch requires a lot of time if you're writing every single piece of code. Just all the authentication workflows can take you a week or more so having all that built out of the box is a huge plus. With Firebase I just had to install a dependency in my front end, and forget about any back end code for authentication, APIs or data storage etc. The only thing I've had to write is Firestore rules (the ones used to control who can do what in your database) and those are super simple to use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation: the &lt;a href="https://firebase.google.com/docs"&gt;official docs&lt;/a&gt; are great and even include some Youtube video series like &lt;a href="https://www.youtube.com/watch?v=v_hR4K4auoQ&amp;amp;list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ"&gt;this one for Firestore&lt;/a&gt;. Also, there are tons of articles and videos on Youtube. My favourite is probably the &lt;a href="https://www.youtube.com/channel/UCsBjURrPoezykLs9EqgamOA"&gt;Fireship.io channel&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Super simple deployments: With the Firebase CLI, deploying a Firebase project is as simple as running &lt;code&gt;firebase deploy&lt;/code&gt;. No need to set up webhooks, clone your repo or anything like that. Just running a script and seeing your project live on a &lt;em&gt;.web.app&lt;/em&gt; domain, even with SSL enabled is awesome.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My 8 tips when working with Firebase
&lt;/h2&gt;

&lt;p&gt;I hope you find the reasons above enough compelling to try Firebase but before that, let me tell you a few tips that I think would make your project development event better:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the Firebase CLI and VSCode extensions
&lt;/h3&gt;

&lt;p&gt;You can install the CLI running &lt;code&gt;npm i firebase-tools -g&lt;/code&gt; and then authenticate running &lt;code&gt;firebase login&lt;/code&gt; with your Google credentials (did I mentioned Firebase is owned by Google?). In addition, the two VSCode extensions I installed are Firebase Explorer and Firestore Rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create two Firebase projects
&lt;/h3&gt;

&lt;p&gt;In order to keep your develop and production environments completely isolated, I'd create two different projects in Firebase (for example &lt;em&gt;myAwesomeApp&lt;/em&gt; and &lt;em&gt;myAwesomeApp-dev&lt;/em&gt;). Each project will have its own database, hosting and, more important, its own quotas so all the tests you'll do will not affect your live environment. You can create the project using the Firebase CLI or, better, create them manually in the &lt;a href="https://console.firebase.google.com/"&gt;Firebase Console website&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vuejs + Firebase project scaffold
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, the project I'm creating is a web built with Vuejs so to start I ran &lt;code&gt;vue create my-project-name&lt;/code&gt;. Then inside the project folder, run &lt;code&gt;firebase init&lt;/code&gt; and selected the features you want, like &lt;em&gt;Hosting&lt;/em&gt; or &lt;em&gt;Firestore&lt;/em&gt; . Next, choose the development project you created in the previous step and finally, the CLI will ask you for the files where it'll define the Firestore rules and indexes. Once your project is scaffolded, you can do your first deployment!&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup deployment scripts for each environment/project
&lt;/h3&gt;

&lt;p&gt;Once your Firebase project is initialized, you can deploy it running &lt;code&gt;firebase deploy&lt;/code&gt;. This is ok to deploy to the Firebase project you chose when you initialized the project, but as we want to target different projects (remember we have develop and production), I suggest to create different scripts in your &lt;em&gt;package.json&lt;/em&gt; file. Here are the ones I have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vue-cli-service serve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vue-cli-service build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vue-cli-service lint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy-rules-dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firebase deploy --project myAwesomeApp-dev --only firestore:rules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy-rules-production"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firebase deploy --project myAwesomeApp --only firestore:rules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy-functions-dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firebase deploy --project myAwesomeApp-dev --only functions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy-functions-production"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firebase deploy --project myAwesomeApp --only functions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy-dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vue-cli-service build --mode development &amp;amp;&amp;amp; firebase deploy --project myAwesomeApp-dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy-production"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vue-cli-service build --mode production &amp;amp;&amp;amp; firebase deploy --project myAwesomeApp"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see the firebase CLI has different flags we can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;--project&lt;/em&gt; is used to select our target project&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;--only&lt;/em&gt; is used to select which part of our project we want to deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use environment variables
&lt;/h3&gt;

&lt;p&gt;This is very obvious but you should use environment variables to load you Firebase project keys or other variables that would be different in each environment. For example, initialise your app like this:&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="c1"&gt;// ⛔️ DONT&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;databaseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storageBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messagingSenderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;measurementId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxXXXXXxxXXXXxxXXXXxxxx&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="c1"&gt;// ✅ DO &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_APIKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_AUTHDOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;databaseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_DATABASEURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_PROJECTID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storageBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_STORAGEBUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messagingSenderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_MESSAGINGSENDERID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_APPID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;measurementId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;VUE_APP_&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;In my case, I'm using Vuejs so I just need to create two files named &lt;em&gt;.env.development&lt;/em&gt; and &lt;em&gt;.env.production&lt;/em&gt; locally and whenever I run &lt;code&gt;npm run build&lt;/code&gt;, it will automatically replace the environment variables with the values from the correspondent file. You can read more about Vuejs environment variables &lt;a href="https://cli.vuejs.org/guide/mode-and-env.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Think twice your data model and don't be afraid to duplicate
&lt;/h3&gt;

&lt;p&gt;Before you start coding, think about how your app is going to look like, which data you are going to need in each page and which pages are going to be more used. This is pretty important because it will affect the way you'll store your data in Firestore (the noSQL database used in Firebase) or the Real Time Database. &lt;/p&gt;

&lt;p&gt;As one of the limitations of the free tier is the number of documents your app reads and writes, consider doing it just when you need it. &lt;/p&gt;

&lt;p&gt;One of the things that have made me save a ton of document reads is duplication of some fields. This is something not very common in relational databases (I'd say it's even forbidden 😅) where we use foreign keys and join queries but it's pretty normal in noSQL databases. You can read more about data modelling and view some videos &lt;a href="https://firebase.google.com/docs/firestore/manage-data/structure-data"&gt;in this section of the docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create functions for your Firestore rules
&lt;/h3&gt;

&lt;p&gt;Once you start defining Firestore rules, there are two functions that you'll use all the time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validate if the request comes from a logged user&lt;/li&gt;
&lt;li&gt;validate if the user accessing a document is the one who created it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that, you can create the following functions in your &lt;em&gt;firestore.rules&lt;/em&gt; file:&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="c1"&gt;//**** Functions   ****//&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isLoggedIn&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isOwner&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&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;You can find more info about security rules &lt;a href="https://firebase.google.com/docs/firestore/security/get-started"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paginate and limit your queries
&lt;/h3&gt;

&lt;p&gt;This comes back to the limitations of the free tier. Just remember to add a &lt;em&gt;limit(x)&lt;/em&gt; to your collection queries whenever you are going to access your data. You don't want to return 150 documents when on your page you can only display 20. &lt;br&gt;
Pagination is super simple to build thanks to the &lt;em&gt;startAfter()&lt;/em&gt; method. Find below an example of how I'm doing pagination in my Vuejs app:&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="c1"&gt;// part of store/index.js file&lt;/span&gt;

&lt;span class="c1"&gt;// global variable to store last paginated element&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;paginationLast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

&lt;span class="c1"&gt;// Vuex store action&lt;/span&gt;
&lt;span class="nx"&gt;getUpcomingTalks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;payload&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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="nx"&gt;talksCollection&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderBy&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asc&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paginationLast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&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="o"&gt;=&amp;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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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="c1"&gt;// save last item for pagination&lt;/span&gt;
          &lt;span class="nx"&gt;paginationLast&lt;/span&gt; &lt;span class="o"&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;docs&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;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

          &lt;span class="nx"&gt;commit&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_UPCOMING_TALKS&lt;/span&gt;&lt;span class="dl"&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;docs&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;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hasMore&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="p"&gt;})&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="nx"&gt;err&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="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;err in action :&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;reject&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;Just remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;limit&lt;/strong&gt; will limit the number of documents returned, pretty straight forward&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startAfter&lt;/strong&gt; will tell Firestore what is the latest document you queried before. In my case, the first time I'll send it &lt;em&gt;null&lt;/em&gt;, so it will start at the beginning of the collection. Then after each successful query, I update it with the last item so the following queries will start from it. &lt;strong&gt;&lt;em&gt;Note&lt;/em&gt; that this has to be a document reference, not an id.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I still have a ton of things to learn about Firebase but I'd say these are the more important things I've learnt so far. Hope you find them useful.&lt;/p&gt;

&lt;p&gt;If you liked this article, you can &lt;a href="https://twitter.com/uf4no"&gt;follow me on Twitter&lt;/a&gt; where I share dev tips an insteresting articles and updates about the progress of my projects 🤙&lt;/p&gt;

&lt;p&gt;Oh! and in case you're wondering, the project I'm building with Firebase is &lt;a href="https://quicktalks.io"&gt;QuickTalks.io&lt;/a&gt; a place to organise and find talks for small audiences.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com/blog"&gt;my blog&lt;/a&gt; where you can find other articles about web development focused on Laravel, Node.js Vue and more.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Improve page performance lazy loading reCaptcha</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Wed, 01 Jul 2020 10:52:08 +0000</pubDate>
      <link>https://dev.to/uf4no/improve-page-performance-lazy-loading-recaptcha-442o</link>
      <guid>https://dev.to/uf4no/improve-page-performance-lazy-loading-recaptcha-442o</guid>
      <description>&lt;p&gt;A few days ago I ran Lighthouse in a couple of my websites and the performance score wasn't very good (around ~50). Most of the recommendations provided to improve the score were server-side, like caching and compressing assets but the score gains when I applied them were not that good. I realized that one the things that what was impacting the website performance the most was reCaptcha.&lt;/p&gt;

&lt;p&gt;I use reCaptcha in every page that contains a form to avoid spam so getting rid of it was not an option. After searching online for some ways to improve the situation, I found &lt;a href="https://tehnoblog.org/google-invisible-recaptcha-how-to-boost-lighthouse-performance-score/" rel="noopener noreferrer"&gt;this article&lt;/a&gt; which explained how to solve all my issues. The solution is awesome by its simplicity: &lt;strong&gt;do not load reCaptcha on the initial page load but lazy load it when the user actually interacts with one of the website forms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's say we have a page with a simple subscription form and we load reCaptcha as it's detailed in their docs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My page&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My awesome website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contactForm"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sub_name"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Johnny Mnemonic"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Email address"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"subEmail"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"eightgigs@memory.com"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"submitBtn"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Subscribe"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;data-sitekey=&lt;/span&gt;&lt;span class="s"&gt;"GOOGLE_RECAPTCHA_KEY"&lt;/span&gt;
        &lt;span class="na"&gt;data-callback=&lt;/span&gt;&lt;span class="s"&gt;"sendForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- reCaptcha API JS --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://www.google.com/recaptcha/api.js"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contactForm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The reCaptcha API library is loaded with the page, just 1.2KB, but it automatically triggers a request to &lt;em&gt;&lt;a href="https://www.gstatic.com/recaptcha/releases/" rel="noopener noreferrer"&gt;https://www.gstatic.com/recaptcha/releases/&lt;/a&gt;&lt;/em&gt; which adds another extra 127KB to our page. And &lt;strong&gt;what happens if the user never interacts with the form? We've loaded a JavaScript file for no reason at all.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The solution is pretty simple and easy to implement:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My page&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My awesome website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contactForm"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"subName"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Johnny Mnemonic"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Email address"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"subEmail"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"eightgigs@memory.com"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"submitBtn"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Subscribe"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;data-sitekey=&lt;/span&gt;&lt;span class="s"&gt;"GOOGLE_RECAPTCHA_KEY"&lt;/span&gt;
        &lt;span class="na"&gt;data-callback=&lt;/span&gt;&lt;span class="s"&gt;"sendForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contactForm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Function that loads recaptcha on form input focus&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reCaptchaOnFocus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;head&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="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;head&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;script&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="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/javascript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.google.com/recaptcha/api.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// remove focus to avoid js error:&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reCaptchaOnFocus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reCaptchaOnFocus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// add initial event listener to the form inputs&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reCaptchaOnFocus&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reCaptchaOnFocus&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let me explain what's happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We're no longer loading the reCaptcha API JS library by default.&lt;/li&gt;
&lt;li&gt;We've declared a function &lt;em&gt;recaptchaOnFocus&lt;/em&gt; that adds the script tag with the reCaptcha API JS library to our page header when it's invoked.&lt;/li&gt;
&lt;li&gt;We've added event listeners in our form inputs to invoke the &lt;em&gt;recaptchaOnFocus&lt;/em&gt; function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way, our initial page load has 2 less requests and we've saved 128KB. For me this was the difference between these two results:&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%2Fantonioufano.com%2Fimage_uploads%2F1593441730947.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%2Fantonioufano.com%2Fimage_uploads%2F1593441730947.png" alt="Lighthouse score"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this helps you improve your page load times but don't apply this just to reCaptcha. &lt;strong&gt;Think about other libraries you might be loading by default in your pages that can be lazy loaded in a similar way.&lt;/strong&gt; I'm sure you'll be able to find some of them that are only necessary in edge cases.&lt;/p&gt;

&lt;p&gt;If you liked this article, you can &lt;a href="https://twitter.com/uf4no" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt; where I share dev tips like this one, updates about my projects and insteresting articles I find online 😎&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com/blog" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; where you can find other articles about web development focused on Laravel, Node.js Vue and more.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building APIs FAST with Strapi, an overview</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Mon, 15 Jun 2020 15:31:40 +0000</pubDate>
      <link>https://dev.to/uf4no/building-apis-fast-with-strapi-an-overview-3cbl</link>
      <guid>https://dev.to/uf4no/building-apis-fast-with-strapi-an-overview-3cbl</guid>
      <description>&lt;p&gt;I've been using &lt;a href="https://strapi.io"&gt;Strapi&lt;/a&gt; for a few months in different scenarios, from quick prototypes and small test to more serious projects like the web application I'm working on (&lt;a href="https://thelifeboard.app"&gt;theLifeBoard.app&lt;/a&gt;). Although I've been using it for months, I haven't written an article about it yet and since a few days ago, the &lt;a href="https://strapi.io/blog/strapi-stable-community-edition?utm_content=130113614&amp;amp;utm_medium=social&amp;amp;utm_source=twitter&amp;amp;hss_channel=tw-3832252517"&gt;Strapi team announced the release of the v3 stable version&lt;/a&gt;, what a better time to write one (or maybe more) than now?&lt;/p&gt;

&lt;p&gt;So what is Strapi? If you're not familiar with it, this is how they define it &lt;a href="https://strapi.io/faq"&gt;in their FAQ&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Strapi is an open-source, Node.js based, headless CMS to manage content and make it available through a fully customizable API. It is designed to build practical, production-ready Node.js APIs in hours instead of weeks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A Content Management System that provides an API in hours&lt;/strong&gt;. Sounds good, doesn't it? Let's create a simple project to get a taste of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick example
&lt;/h2&gt;

&lt;p&gt;Let's say we want to build an API to manage books. Only authenticated users can create, edit or delete them but anyone can query them. Our endpoints would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public

&lt;ul&gt;
&lt;li&gt;GET /books -&amp;gt; Returns all books&lt;/li&gt;
&lt;li&gt;GET /books/:id -&amp;gt; Returns a specific book by id&lt;/li&gt;
&lt;li&gt;GET /books/count -&amp;gt; Returns the number of books&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Auth protected

&lt;ul&gt;
&lt;li&gt;POST /books -&amp;gt; Create a new book&lt;/li&gt;
&lt;li&gt;PUT /books/:id -&amp;gt; Edit a book by id&lt;/li&gt;
&lt;li&gt;DELETE /books/:id -&amp;gt; Delete a book by id&lt;/li&gt;
&lt;/ul&gt;


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

&lt;h3&gt;
  
  
  Database setup
&lt;/h3&gt;

&lt;p&gt;The first thing we need is a database to store our data. Strapi supports SQLite, MySQL, PostgreSQL and MongoDB so you can use any of them. For this example, I'm going to use MongoDB running on Docker, which, for me, it's the easiest way to manage database services in a local development environment. You can check &lt;a href="https://antonioufano.com/articles/my-dev-environment-in-windows-10-wsl2-and-docker-31"&gt;this article&lt;/a&gt; in which I explain how to install Docker among other things.&lt;/p&gt;

&lt;p&gt;Once you have Docker running, follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;docker pull mongo&lt;/code&gt; to download the latest MongoDB image&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;docker volume create mongoData&lt;/code&gt; to create persistent volume in which we'll store the data so it doesn't get lost when we stop/delete our container&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sudo docker run --rm -d -p 27017:27017/tcp -v mongoData:/data/db mongo:latest&lt;/code&gt; to start the MongoDB container. The -p option will map the local port 27017 to the container's port and the -v option will map the local volume mongoData we just created, to the data/db folder in the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scaffold the Strapi project
&lt;/h3&gt;

&lt;p&gt;To initialize and generate the project we need to run &lt;code&gt;npx create-strapi-app your_project_name&lt;/code&gt;. For example &lt;code&gt;npx create-strapi-app demo-strapi&lt;/code&gt;. This will start a guided installation. Choose Custom type and select the options as detailed below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose your installation type: Custom (manual settings)&lt;/li&gt;
&lt;li&gt;Choose your default database client: mongo&lt;/li&gt;
&lt;li&gt;Database name: demo-strapi&lt;/li&gt;
&lt;li&gt;Host: 127.0.0.1&lt;/li&gt;
&lt;li&gt;srv connection: false&lt;/li&gt;
&lt;li&gt;Port (It will be ignored if you enable srv): 27017&lt;/li&gt;
&lt;li&gt;Username:  (your database user, default is empty)&lt;/li&gt;
&lt;li&gt;Password: (your database password, default is empty)&lt;/li&gt;
&lt;li&gt;Authentication database: admin&lt;/li&gt;
&lt;li&gt;Enable SSL connection: No&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The installation will take a few minutes. Once finished you can start your app running &lt;code&gt;npm run develop&lt;/code&gt; from the project folder and the application will start in port 1337 by default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4JosS_OJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869011994.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4JosS_OJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869011994.png" alt="Strapi first run"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first time we start a Strapi application, it will ask you to create an administrator user. This will be the user we'll use to create content types, manage permissions, install plugins etc. &lt;/p&gt;

&lt;p&gt;Once the user is created, we'll be in the administration UI and we can start creating our Book model in the Content-Types Builder section. Select Create new collection type and enter the name of the model, &lt;strong&gt;in singular&lt;/strong&gt;. In our case, it would be Book. &lt;/p&gt;

&lt;p&gt;Next you'll have to select the different attributes and types for our Book model. I selected the following ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;title 

&lt;ul&gt;
&lt;li&gt;Type: text (short). &lt;/li&gt;
&lt;li&gt;Advanced settings: Required field, Unique field&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;pages

&lt;ul&gt;
&lt;li&gt;Type: number (integer)&lt;/li&gt;
&lt;li&gt;Advanced settings: Private field&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;publishDate

&lt;ul&gt;
&lt;li&gt;Type: date (date)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;summary:

&lt;ul&gt;
&lt;li&gt;Type: Rich text&lt;/li&gt;
&lt;li&gt;Advanced settings: Required field&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, in the advanced settings &lt;strong&gt;we're adding some validations for required and unique fields. We can event include more detailed validations by using a RegExp&lt;/strong&gt; pattern if we want. &lt;/p&gt;

&lt;p&gt;Once our model is defined, click save and the application will restart. &lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Content within the UI
&lt;/h3&gt;

&lt;p&gt;Now that we have created our Book Content Type we can start adding books directly from the administration UI. Go to the Book Collection Type and you'll see an "Add New Book" button, which will show a form like the one below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_XAb6w4t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869138334.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_XAb6w4t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869138334.png" alt="Strapi Content Management"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the input fields match with the data types of the attributes we selected while creating the content type. From this section of the UI you'll be able to create, edit or delete items for all the content types, which is great but the best part is that &lt;strong&gt;Strapi has also generated a REST API we can use to interact with the content types&lt;/strong&gt;. Let's review how it looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strapi project structure
&lt;/h3&gt;

&lt;p&gt;Once you've created the first model, if you open the project folder, you'll see that it has generated two folders: &lt;strong&gt;config&lt;/strong&gt; and an &lt;strong&gt;api&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Inside the &lt;em&gt;config&lt;/em&gt; folder you'll find the application server specific configuration, like the database details (the ones you entered during the installation wizard), the host, port and even a &lt;em&gt;cron.js&lt;/em&gt; file in which you can specify scheduled functions to run.&lt;/p&gt;

&lt;p&gt;Inside the &lt;em&gt;api&lt;/em&gt; folder you'll see one folder for each content type created, in this case one named &lt;em&gt;book&lt;/em&gt;. Each one will have the following folders inside: &lt;em&gt;config&lt;/em&gt;, &lt;em&gt;controllers&lt;/em&gt;, &lt;em&gt;models&lt;/em&gt; and &lt;em&gt;services&lt;/em&gt;. Inside these folders we have the files we can modify to extend and customize our API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;em&gt;book/config/routes.json&lt;/em&gt; we have all our endpoint definitions. Each route has a method, path and handler, which by default points to methods in the controller.&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;book/controllers/book.js&lt;/em&gt; we can create the methods that will handle the request to our routes By default, this file is empty as it extends the default Strapi controller which has the following methods: find(), count(), findOne(), create(), update() and delete(). You can extend the functionality of these methods by creating them in this file and adding your own logic (more info &lt;a href="https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Inside &lt;em&gt;book/models/&lt;/em&gt; we have the books.settings.json which contains the model attributes we defined earlier (title, pages, summary and publishDate) and the book.js model file in which we can add lifecycle hooks, like sending an email every time a book is created for example (more info &lt;a href="https://strapi.io/documentation/v3.x/concepts/models.html#life-cycle-callbacks"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Similar as with the controller, in the &lt;em&gt;book/services&lt;/em&gt; folder you'll find an empty file book.js, as it extends the default Strapi core service which contains all the following methods: find(), findOne(), count(), create(),update(), delete(), search(), countSearch() (more info &lt;a href="https://strapi.io/documentation/v3.x/concepts/services.html#core-services"&gt;here&lt;/a&gt;). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, the generated REST API will manage basic CRUD operations (Create, Read, Update and Delete) so if this is all you need, you're ready to go 😉.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the permissions
&lt;/h3&gt;

&lt;p&gt;Permission management is Strapi is a pretty straight forward in the Roles &amp;amp; Permissions section of the UI. &lt;br&gt;
We have two different roles available by default: Authenticated and Public, although we can create as many roles as we want.&lt;br&gt;
To allow actions in each role, select the role and in the Permissions section, you'll see all the Content Types available and all its available routes. You'll just need to select the permitted routes for each role and click save. In my example, I'm allowing all actions for the Authenticated role:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--McBHWPaw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869181703.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--McBHWPaw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869181703.png" alt="Strapi permission management"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And just the count, find and find one actions for the Public role:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JoeTrqyZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869220178.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JoeTrqyZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869220178.png" alt="Strapi permission management"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once permissions are configured, our API is ready to test. But how do we create the users for the Authenticated role? Let's see check that out.&lt;/p&gt;
&lt;h3&gt;
  
  
  Registering and login users
&lt;/h3&gt;

&lt;p&gt;Strapi provides default endpoints to manage API users. These are:&lt;/p&gt;

&lt;p&gt;Register: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method: POST&lt;/li&gt;
&lt;li&gt;Endpoint: /auth/local/register&lt;/li&gt;
&lt;li&gt;Required body:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;   
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Login:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method: POST&lt;/li&gt;
&lt;li&gt;Endpoint: /auth/local&lt;/li&gt;
&lt;li&gt;Required body:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;   
    &lt;/span&gt;&lt;span class="nl"&gt;"identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the identifier could be the username or the email.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both return a similar response, including the user details, role and a JWT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jwt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJIUzI1NiIsCI6IkpXVCJ9....."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"confirmed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"blocked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"one@a.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5ee0cafb6ec1410fda381181"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authenticated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Default role given to authenticated user."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authenticated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       
            &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5ee0c6f136637b0e7426a2a5"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Strapi also provides endpoints to for the forget and reset password functionalities. You can find &lt;a href="https://strapi.io/documentation/3.0.0-beta.x/plugins/users-permissions.html#password-reset"&gt;more info here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;We'd need to send the JWT in the Authorization header in order to access the authenticated protected routes&lt;/strong&gt;. If we send a request with no JWT (or with an invalid one) to a route only allowed to the authorized role, we'll receive a 403 Forbidden error.&lt;/p&gt;

&lt;p&gt;Now we should be able to register a new user, login and with the JWT recived, send a POST request to create a Book. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--llWsteek--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869275326.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--llWsteek--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://antonioufano.com/image_uploads/1591869275326.png" alt="Strapi Postman API request"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To query the API we just need to send a GET request to /books, no Authentication required.&lt;/p&gt;

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

&lt;p&gt;This was just a quick overview of what Strapi offers. A quick summary would be that &lt;strong&gt;Strapi allows you to generate a Node.js REST API with authentication in a matter of minutes&lt;/strong&gt;. That's the main sell point for me but it offers a lot more. The administration UI allows you to manage the content without the need to create a front end yourself.  It has model relationships out of the box, plugins to manage permissions, send emails, manage media files, use OAuth authentication from different providers, GraphQL, Cron jobs and more. These means that &lt;strong&gt;you can basically build and entire back-end for your service or application with Strapi&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It's built in Node.js (for me, it's a pro 😀)&lt;/li&gt;
&lt;li&gt;It's easy to extend the logic of your models or create new endpoints&lt;/li&gt;
&lt;li&gt;File structure is very simple, mainly: api/model_name/* and /config&lt;/li&gt;
&lt;li&gt;Support for .env files, which makes it super easy to deploy to different environments&lt;/li&gt;
&lt;li&gt;Model relationships built within the UI&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://slack.strapi.io/"&gt;The community in Slack&lt;/a&gt; is active and super helpful&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The not-so-good
&lt;/h3&gt;

&lt;p&gt;Strapi is great, but it's not perfect. Here are a few of the downsides I've faced while using it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's not built in TypeScript&lt;/li&gt;
&lt;li&gt;Model relationship management from the UI is not perfect and sometimes requires you to manually modify the model JSON files. Check the &lt;a href="https://strapi.io/documentation/v3.x/concepts/models.html#relations"&gt;model relationships section of the documentation&lt;/a&gt; if you have any issues.&lt;/li&gt;
&lt;li&gt;It may be difficult to find answers online when you face weird errors. Luckily you can go to &lt;a href="https://slack.strapi.io/"&gt;the Slack channel&lt;/a&gt; and ask for help.&lt;/li&gt;
&lt;li&gt;Documentation is good but not perfect although the team keeps polishing it and adding more guides and examples very often. Sometimes I find myself looking at older versions of the docs because they appear first on Google 😐&lt;/li&gt;
&lt;li&gt;API permissions are stored in the DB which means that when we want to deploy a new version of your API which includes permission changes we'll have to manually apply them using the UI in Production or via a database migration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope this article helps you decide if Strapi is a good choice for your next project. I really recommend it as it could help you save time developing APIs (I can't remember how many times I've scaffolded an Express app...). However if you're new to back-end development, you might get lost if you try to add custom functionalities so make sure you get familiar with concepts like routing, controllers, services etc by &lt;a href="https://strapi.io/documentation/v3.x/getting-started/quick-start.html"&gt;reading the docs first&lt;/a&gt;. And if you have any questions, you can &lt;a href="https://twitter.com/uf4no"&gt;ping me on Twitter&lt;/a&gt; or ask the community in the &lt;a href="https://slack.strapi.io/"&gt;Slack help channel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you liked this article, you can &lt;a href="https://twitter.com/uf4no"&gt;follow me on Twitter&lt;/a&gt; where I share dev tips an insteresting articles I find online 😎&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com/blog"&gt;my blog&lt;/a&gt; where you can find other articles about web development focused on Laravel, Node.js Vue and more.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Public demo of our side project (no signup required)</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Tue, 21 Apr 2020 14:58:52 +0000</pubDate>
      <link>https://dev.to/uf4no/public-demo-of-our-side-project-no-signup-required-3o98</link>
      <guid>https://dev.to/uf4no/public-demo-of-our-side-project-no-signup-required-3o98</guid>
      <description>&lt;p&gt;We've been working in our side project called &lt;a href="https://thelifeboard.app"&gt;theLifeBoard&lt;/a&gt; for a few months, just doing little bits whenever we can. It's a web application to help people identify their goals and help them achieve them by providing a dashboard to organize their weeks and curated resources. &lt;/p&gt;

&lt;p&gt;We have a version of the web app that we've been using for a few weeks, but it still has a lot of missing features and some nasty bugs.&lt;/p&gt;

&lt;p&gt;A couple of days ago I had an idea. As the application is not ready to be shared with real users we thought about building a public demo, one that &lt;strong&gt;doesn't require users to sign up&lt;/strong&gt; but that allows them to play around with the dashboard and maybe give us some feedback.&lt;/p&gt;

&lt;p&gt;It just took me a couple of days to remove all the API integrations and add it to our landing page. You can find it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thelifeboard.app/demo"&gt;https://thelifeboard.app/demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to play with it and let us know what you think. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The data will not be sent to our back end and will be lost if you refresh the page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Tech stack
&lt;/h3&gt;

&lt;p&gt;If you're interested in the technical side, the front end it's built with Vue.js + Vuetify and the back end is composed of multiple APIs some of them built with Strapi and others are simple Lambda functions.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>vue</category>
    </item>
    <item>
      <title>Create a markdown blog with Laravel 7</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Mon, 13 Apr 2020 09:19:15 +0000</pubDate>
      <link>https://dev.to/uf4no/create-a-markdown-blog-with-laravel-7-4ib5</link>
      <guid>https://dev.to/uf4no/create-a-markdown-blog-with-laravel-7-4ib5</guid>
      <description>&lt;p&gt;For the last three years, my blog was built using a javascript rich text editor that generated all the HTML code for my posts. I've never been 100% happy with it as the editor generated a lot of unnecessary HTML and the styling of code blocks was not pretty good. In addition, I share my posts here in Dev.to and even though sometimes I can copy/paste my HTML articles, some parts were not styled properly. &lt;/p&gt;

&lt;p&gt;I've always enjoyed taking notes in Markdown so I decided to give it a try for my blog. I created a quick demo with a Markdown editor and then parsed the articles to HTML using GitHub's public API. I gotta say the writing experience is way better than what I had before. Cross posting to Dev.to is a lot easier now and I can use GitHub's code highlighting and styling. See below how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstraping a Laravel blog (model, controller and routes)
&lt;/h2&gt;

&lt;p&gt;First thing we need is a bootstrapped Laravel app. You can check &lt;a href="https://antonioufano.com/articles/bootstrap-a-laravel-7-tailwind-css-project-25"&gt;this previous article&lt;/a&gt; in which I create a Laravel + Tailwind CSS project.&lt;br&gt;
Once we have our bootstrapped project, lets focus in the blog. First we'd need to create a migration to create the articles table . You can generate one with the running &lt;code&gt;php artisan make:migration create_articles_table&lt;/code&gt; .&lt;br&gt;
This will create a new file in the &lt;em&gt;database/migrations&lt;/em&gt; folder. Edit it to include all the fields we're going to need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// database/migrations/2020_xx_xx_xxxxxx_create_articles_table.php 

&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Migrations\Migration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Schema\Blueprint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateArticlesTable&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Run the migrations.
     *
     * @return void
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//to generate seo friendly urls&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'body_md'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// the article body in mardkdown&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'summary_md'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// the summary in markdown&lt;/span&gt;
                        &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'body_html'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//the article body in html&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'summary_html'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//the summary in html&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'online'&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="c1"&gt;// we'll use this to have articles published or in draft mode&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&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="cd"&gt;/**
     * Reverse the migrations.
     *
     * @return void
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dropIfExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles'&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;Make sure your database connection details are correctly configured in your .env file and then run &lt;code&gt;php artisan:migrate&lt;/code&gt; to create the table in the database.&lt;br&gt;
Next step is to create the Model and Controller with &lt;code&gt;php artisan make:model Article --resource&lt;/code&gt;. This will generate a model in &lt;em&gt;/app/Article.php&lt;/em&gt; folder and the controller in &lt;em&gt;/app/Http/Controllers/ArticleController.php&lt;/em&gt; .&lt;br&gt;
Now we need to create the routes that we'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return the page the contains the form to create a new article&lt;/li&gt;
&lt;li&gt;Endpoint to store the article&lt;/li&gt;
&lt;li&gt;Return the page to display the list of articles&lt;/li&gt;
&lt;li&gt;Return the page to display a single article&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add them in the &lt;em&gt;/routes/web.php&lt;/em&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/articles/create'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ArticleController@create'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.create'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ArticleController@store'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.store'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ArticleController@index'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.index'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/articles/{param}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ArticleController@show'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.show'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The blog index page
&lt;/h2&gt;

&lt;p&gt;As we've detailed in the routes file (&lt;em&gt;/routes/web.php&lt;/em&gt;), to retrieve the articles index page we'll call the index method in the ArticleController. The controller would have to query the database and retrieve all the published articles (articles with the online field to true) and pass them to the view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Article&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//query the database&lt;/span&gt;
        &lt;span class="nv"&gt;$articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'updated_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'online'&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="c1"&gt;//return the view passing the list of articles&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$articles&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;In the blog index page, we'll display the title, summary_html and the date of all the articles. Let's create the view file in &lt;em&gt;/resources/views/articles/index.blade.php&lt;/em&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// resources/views/articles/index.blade.php&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'layouts.app'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-gray-800 px-8 md:w-2/3 lg:w-3/5 xl:w-1/2 sm:w-full mx-auto"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-center"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-center text-3xl my-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-3 hover:underline"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;{route('articles.create')}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="nc"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$articles&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"border-b"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-3xl font-bold text-center mt-4 hover:underline"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{Route('articles.show', &lt;/span&gt;&lt;span class="nv"&gt;$article-&amp;gt;slug&lt;/span&gt;&lt;span class="s2"&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="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-sm text-center leading-5 text-gray-700 mt-3"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Posted&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d/m/Y'&lt;/span&gt;&lt;span class="p"&gt;)}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"markdown-body"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary_html&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-right mt-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{Route('articles.show', &lt;/span&gt;&lt;span class="nv"&gt;$article-&amp;gt;slug&lt;/span&gt;&lt;span class="s2"&gt;)}}"&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"px-4 py-2 border border-gray-300 rounded bg-white text-sm font-medium text-gray-700 hover:border-gray-500 focus:z-10 focus:outline-none focus:border-gray-300 focus:shadow-outline-gray active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nc"&gt;Read&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endforeach&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;endsection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I'm using a lot of css classes from TailwindCSS. You can ignore them if you're using another CSS library (like Bootstrap or Bulma).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important part in this view is the &lt;em&gt;foreach&lt;/em&gt; loop that will will print the section inside it for every article we pass to the view.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blog article page
&lt;/h2&gt;

&lt;p&gt;The blog article page will be very similar to the index one, although in this case controller would have to filter for a specific article using the parameter we'll pass in the URL (note the route is &lt;em&gt;/articles/{param}&lt;/em&gt;). This parameter could be the article id or the slug so we can access the articles with &lt;em&gt;/articles/1&lt;/em&gt; or &lt;em&gt;/articles/this-is-the-title&lt;/em&gt; 😉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//seach by id or the slug&lt;/span&gt;
        &lt;span class="nv"&gt;$articleFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;firstOrFail&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="nv"&gt;$articleFound&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;online&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="c1"&gt;//if article is published, go to article page&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.show'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'article'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$articleFound&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;//if article not published, redirect to articles.index page&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.index'&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;In the view (&lt;em&gt;show.blade.php&lt;/em&gt; file), we'll display the article's title, the date (using the updated_at) and the body_html fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// resources/views/articles/show.blade.php&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'layouts.app'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-gray-800 md:w-2/3 lg:w-3/5 xl:w-1/2 sm:w-full mx-auto"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-left pb-4 mt-4"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-sm leading-5 text-gray-700 hover:underline"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ url()-&amp;gt;previous() }}"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Back&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-5xl text-center "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-sm text-center leading-5 text-gray-700 mt-3"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Posted&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d/m/Y'&lt;/span&gt;&lt;span class="p"&gt;)}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"markdown-body"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body_html&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;endsection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we have 2 out of 4 routes done, the next one will be the one that returns the page that contains the form to create a new article&lt;/p&gt;

&lt;h2&gt;
  
  
  The new article page with markdown editor
&lt;/h2&gt;

&lt;p&gt;At the controller level, this is pretty straight forward as we don't need to query the database. We just have to return a page that will contain the form to create the new article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;create&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="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.create'&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;The view we'll include a form which action would be the articles.store route. The form will have inputs for all the fields we want: title, summary, body and the online indicator. &lt;/p&gt;

&lt;p&gt;For the summary and body fields we'll use textarea inputs and we'll include a mardown editor in them. I've chosen &lt;a href="https://simplemde.com/"&gt;SimpleMDE&lt;/a&gt; as (as the name indicates) it's super simple to integrate, has a toolbar with shortcuts to most of the things I'd need and it can be customized to your needs (see &lt;a href="https://github.com/sparksuite/simplemde-markdown-editor"&gt;documentation in GitHub&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'layouts.app'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-center text-3xl font-semibold"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full px-6"&lt;/span&gt;  &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;{route('articles.store')}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt; &lt;span class="n"&gt;enctype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;csrf&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-wrap -mx-3 mb-6"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full md:w-4/5 px-3 mb-6 md:mb-0"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nc"&gt;Title&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-red-500 text-xs italic"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Please&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full md:w-1/5 px-3 mb-6 md:mb-0"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"online"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nc"&gt;Online&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"relative"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight "&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"online"&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"online"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;No&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Yes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;svg&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"fill-current h-4 w-4"&lt;/span&gt; &lt;span class="n"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="n"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 0 20 20"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;svg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-wrap -mx-3 mb-6"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full px-3"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid-password"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-gray-600 text-xs italic mb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;This&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"input "&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;old&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'body'&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;lt;/&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"flex flex-wrap -mx-3 mb-6"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full px-3"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"grid-password"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nc"&gt;Summary&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-gray-600 text-xs italic mb-2"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;This&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;appear&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"input "&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"summary"&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"summary"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;old&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'summary'&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;lt;/&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full text-right pa-3 mb-6"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"btn btn-green my-4"&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Save article"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nc"&gt;Import&lt;/span&gt; &lt;span class="no"&gt;CSS&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="no"&gt;JS&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nc"&gt;SimpleMDE&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"stylesheet"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;// Initialise editors&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bodyEditor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleMDE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;summaryEditor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleMDE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"summary"&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;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;endsection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; At the end of the file you can see I import the CSS and JS files required for the editor and create new instances of them targeting the textarea elements using their id's&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, if you open this page (localhost:8000/articles/create) you should see the form with the mardown editors in the body and summary fields. &lt;/p&gt;

&lt;h2&gt;
  
  
  Storing articles and parsing Markdown to HTML
&lt;/h2&gt;

&lt;p&gt;In the editors of the form we just created we'll write Mardown but, in the index and show views we're going to display the html fids (remember we're displaying the &lt;em&gt;sumary_html&lt;/em&gt; and &lt;em&gt;body_html&lt;/em&gt; fields from the database). So &lt;strong&gt;how can we parse the markdown to HTML 🤔?&lt;/strong&gt; and &lt;strong&gt;when do we parse it?&lt;/strong&gt; For both questions I found different options. &lt;/p&gt;

&lt;p&gt;How to parse it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create our own parser&lt;/li&gt;
&lt;li&gt;Use an existing library (like &lt;a href="https://parsedown.org/"&gt;Parsedown&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Use GitHub's API via &lt;a href="https://github.com/calebporzio/gitdown"&gt;GitDown&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When to parse it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse the markdown to HTML every time we display the index or show pages&lt;/li&gt;
&lt;li&gt;Parse the markdown to HTML before storing it in the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end I opted to use GitHub's API and parse it before storing the articles in the database. There is package called GitDown that will simplify it a lot, so we'd have to install it via compose with &lt;code&gt;composer require calebporzio/gitdown&lt;/code&gt; . Once installed, we can go ahead and create the store method of the ArticleController, the action of the form we just created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Http/Controllers/ArticleController.php&lt;/span&gt;

&lt;span class="c1"&gt;// import GitDown dependency&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GitDown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Article&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//I'm just validating the presence of the title, but you can validate more fields&lt;/span&gt;
        &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;//create new Article instance and assign values&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body_md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary_md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;online&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;online&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;//generate article slug for nice URLs&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str_slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;//parse body and summary to HTML via GitHub API&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GitDown&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parseAndCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GitDown&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parseAndCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

        &lt;span class="c1"&gt;//save article&lt;/span&gt;
        &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles.index'&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; we're using the parseAndCache method exposed by the GitDown class and pass it the body and summary fields of the request we received. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In order to use the &lt;code&gt;str_slug()&lt;/code&gt; function, we'd need to install the laravel/herlpers dependency via composer: &lt;code&gt;composer require laravel/helpers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about code highlighting?&lt;/strong&gt; Luckily for us, GitDown ships with the helper @gitdown that we can include in the header of our views to add all the CSS classes we need (&lt;a href="https://github.com/calebporzio/gitdown#markdownsyntax-css"&gt;see docs&lt;/a&gt;). &lt;br&gt;
Have you noticed that the divs that contain the body and summary or the articles have a class &lt;em&gt;markdown-body&lt;/em&gt;? This is the class targeted by the GitDown CSS.&lt;/p&gt;

&lt;p&gt;Of couse, you can use a different class and style everything as you want, or override just some of the classes as you wish, that's up to you 😉.&lt;/p&gt;

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

&lt;p&gt;There are a few things I havent covered in this article, like authentication so only logged users can create new articles or edit/delete articles, but there are plenty of articles out there that explain those concepts. I wanted to focus in the Markdown/HTML part. You can find the whole code of this article in &lt;a href="https://github.com/uF4No/markdown-blog-laravel"&gt;this repo in GitHub&lt;/a&gt;. Feel free to clone/download it and use it a base for your projects. Hope you found this useful.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com"&gt;my website&lt;/a&gt;. For dev tips and interesting articles &lt;a href="https://twitter.com/uf4no"&gt;follow me on Twitter&lt;/a&gt; 😎.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Bootstrap a Laravel 7 + Tailwind CSS project</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Thu, 19 Mar 2020 19:08:11 +0000</pubDate>
      <link>https://dev.to/uf4no/bootstrap-a-laravel-7-tailwind-css-project-5efa</link>
      <guid>https://dev.to/uf4no/bootstrap-a-laravel-7-tailwind-css-project-5efa</guid>
      <description>&lt;p&gt;I've decided to redesign and upgrade my website with Tailwind CSS and Laravel 7 so here is a quick guide of how to bootstrap a project that uses both of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Laravel 7
&lt;/h2&gt;

&lt;p&gt;The first thing to do is to make sure your system meets the requirements, mainly having PHP 7.2, composer and some extensions. Then as detailed in the &lt;a href="https://laravel.com/docs/7.x/installation"&gt;Laravel official docs&lt;/a&gt; run &lt;em&gt;composer global require laravel/installer&lt;/em&gt;  and then use &lt;em&gt;laravel new my-project&lt;/em&gt; to generate the project scaffold.&lt;/p&gt;

&lt;p&gt;Additionally, you can run &lt;em&gt;composer create-project --prefer-dist laravel/laravel my-project&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Once your project is created, you can run &lt;em&gt;php artisan serve&lt;/em&gt; from the project folder to start a local server and open localhost:8000 in your browser to see the default Laravel welcome page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Tailwind CSS to your Laravel 7 project
&lt;/h2&gt;

&lt;p&gt;The next step is to add Tailwind to the project. I found plugin by Jeffrey Way that makes this super easy. Install it with &lt;em&gt;npm install laravel-mix-tailwind --save-dev&lt;/em&gt; and then modify the file webpack.mix.js so it's like the next one:&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;mix&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;laravel-mix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// for Tailwind: https://github.com/JeffreyWay/laravel-mix-tailwind&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;laravel-mix-tailwind&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/js/app.js&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;public/js&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;sass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/sass/app.scss&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;public/css&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;tailwind&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what is this for? If you check the package.json file of your project, you can see that there is a dev/development script. These scripts run Webpack which is a module bundler that will take care of compiling our css or scss and js files in the resources folder and put the results in the public directory. It's normally used for javascript but we're going to use it for our css for now.  &lt;/p&gt;

&lt;p&gt;As detailed in the Tailwind docs, we need to import the Tailwind base and components to our css or scss file. Inside the resources folder, create a folder named "scss" and inside it, create a file named app.scss. In this file copy the following imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* 
* resources/sass/app.scss
*/&lt;/span&gt;

&lt;span class="c1"&gt;// Imports Tailwind CSS&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="nt"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="nt"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to generate a Tailwind config file. We can create it manually or we can generate it with the command &lt;em&gt;npx tailwindcss init&lt;/em&gt;.  This will generate an empty file but Tailwind allows you to customize your fonts, colours and a lot of other things in this file so check the &lt;a href="https://tailwindcss.com/docs/configuration/"&gt;corresponding page of their official docs&lt;/a&gt; for more info.  &lt;/p&gt;

&lt;p&gt;Now if you run &lt;em&gt;npm run dev&lt;/em&gt; , Webpack should start to compile and generate the files in your public folder.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The current version of laravel-mix-tailwind contains a bug as it expects the file tailwind.config.js to be named tailwind.js. The error in the console is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;_Error: Specified Tailwind config file &lt;span class="s2"&gt;"/Projects/my-project/tailwind.js"&lt;/span&gt; doesn&lt;span class="s1"&gt;'t exist._
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can rename the file to match what it expects or you can change the line where it's hardcoded in the file /node_modules/laravel-mix-tailwind/index.js (line 14).&lt;/p&gt;




&lt;p&gt;After Webpack finishes compiling, if you check the file public/app.css you can see there are a lot of classes in it (it will have more than 65k lines!). That's Tailwind being added to your project. Now if you include the app.css file in your views, you'll be able to use all the Tailwind classes 💪 .  &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding PurgeCSS to your Laravel 7 project
&lt;/h2&gt;

&lt;p&gt;One of the inconveniences of Tailwind CSS is its size. As it has so many classes the size of the whole library is very big but we can use purgeCSS to remove all classes we are not using in our project. Similar to what we did to install Tailwind, we need to install a Laravel Mix plugin, in this case with the command &lt;em&gt;npm i laravel-mix-purgecss --save-dev&lt;/em&gt;. Once installed, we need to add a few lines to our webpack.mix.js file:&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;mix&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;laravel-mix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// for Tailwind: https://github.com/JeffreyWay/laravel-mix-tailwind&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;laravel-mix-tailwind&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// for PurgeCSS&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;laravel-mix-purgecss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/js/app.js&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;public/js&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;sass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/sass/app.scss&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;public/css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// .less('resources/less/app.less', 'public/css')&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tailwind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;purgeCss&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inProduction&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;folders&lt;/span&gt;&lt;span class="p"&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;src&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;templates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&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;twig&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;html&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;js&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;php&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;vue&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;span class="nx"&gt;setPublicPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, we'll only run purgeCSS when we generate our production build with &lt;em&gt;npm run prod&lt;/em&gt;. Go ahead and run it and compare the file sizes with the ones generated with &lt;em&gt;npm run dev&lt;/em&gt; . In my case, the difference in the app.css file is 3.59Kb in production and 1.23Mb in development. That'll make a huge difference in the loading time of our web!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: if you want to add a more specific config to purgeCSS, you can create a file named postcss.config.js as detailed &lt;a href="https://tailwindcss.com/docs/controlling-file-size"&gt;in this link&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Hope you find this useful. I've uploaded a scaffolded project to &lt;a href="https://github.com/uF4No/laravel7-tailwind"&gt;this public repo in GitHub&lt;/a&gt; so you can just clone it and start using it as the base for your next project. Just remember to follow the instructions in the Readme to install all dependencies!  &lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com"&gt;my website&lt;/a&gt;. If you like it, you may find interesting previous articles in &lt;a href="https://antonioufano.com/blog"&gt;my blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>beginners</category>
      <category>css</category>
    </item>
    <item>
      <title>How I built a new year resolutions exercise with Vue, Node and AWS</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Mon, 06 Jan 2020 17:20:22 +0000</pubDate>
      <link>https://dev.to/uf4no/how-i-built-a-new-year-resolutions-exercise-with-vue-node-and-aws-1k96</link>
      <guid>https://dev.to/uf4no/how-i-built-a-new-year-resolutions-exercise-with-vue-node-and-aws-1k96</guid>
      <description>&lt;p&gt;I've been working in a side project called &lt;a href="https://thelifeboard.app"&gt;the LifeBoard&lt;/a&gt; for a few weeks now. Its main purpose is to &lt;strong&gt;help people identify and achieve their goals by creating habits&lt;/strong&gt;. We know this seems like a very wide and difficult to tackle problem but we think it's just a matter of creating a service focused on few key principles we've been following in our life for the last couple of years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Asking the right questions&lt;/li&gt;
&lt;li&gt;  Organisation &amp;amp; motivation&lt;/li&gt;
&lt;li&gt;  Sharing your success and the failures&lt;/li&gt;
&lt;li&gt;  Reward consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our service is still far from being ready for users but we wanted to launch something that users could benefit from before the end of the year and also check if it triggered some interest so, being almost at the end of the year, we decided &lt;strong&gt;we could create a new year resolutions exercise&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;This is something that we do every year and we really like to reflect back and review the things that we did, which ones we completely forgot or couldn't do, how our preferences have changed and analyse why and in general, see how we as persons have changed in the last 365 days. So we thought that including this exercise as part of our project would be a very good introduction letter to potential users.&lt;/p&gt;

&lt;p&gt;When working in a big project, I usually do a detailed technical architecture but when I'm building something small I jump straight to my code editor and improvise the architecture as I progress. &lt;strong&gt;My main goal with this article is to explain all the different options I explored while I was coding&lt;/strong&gt;, taking into account pros and cons of each, like if the time invested to implement a more robust design is worth the received return.&lt;/p&gt;

&lt;p&gt;If you just want to see the exercise, you can find it &lt;a href="https://thelifeboard.app/newyear"&gt;in this link&lt;/a&gt; but if you're interested in how I built it and the different options I explored during the process, just keep reading 😉  &lt;/p&gt;

&lt;h3&gt;
  
  
  What is the exercise about?
&lt;/h3&gt;

&lt;p&gt;The idea was pretty basic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Anyone can visit the exercise page, no login required  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The exercise is a step-by-step questionnaire and it's meant to be completed in one go  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the end of the exercise, we ask for the user's name and email before storing the responses in our database&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send a confirmation email right after the exercise is completed  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One year after completion, user's will receive an email with their details  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these things in mind I jumped to code. We already had a website with a landing page built in Vue.js (bootstrapped with Vue CLI) which includes Vue Router, so adding a new page is as simple as creating a new file in the views folder named NewYear.vue and include a new route in the router.js file pointing to it:&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="c1"&gt;// router.js file&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&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;vue&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;Router&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;vue-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="c1"&gt;// import views&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Home&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;./views/Home.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NewYear&lt;/span&gt; &lt;span class="o"&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/views/NewYear.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Router&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;history&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&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;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;routes&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="na"&gt;path&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="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;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;component&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="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/newyear&lt;/span&gt;&lt;span class="dl"&gt;'&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;year review and planning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NewYear&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;That's all needed to add new pages to a Vue.js project. Now I just had to build the exercise in the NewYear.vue file  &lt;/p&gt;

&lt;h3&gt;
  
  
  Building the exercise view
&lt;/h3&gt;

&lt;p&gt;I wanted to divide the exercise in multiple steps so users are aware of how many remaining questions they have left. I could have built something from scratch but, I was already using Vuetify in the landing page and luckily for me, it contains a &lt;a href="https://vuetifyjs.com/en/components/steppers"&gt;stepper component&lt;/a&gt; which does exactly what I wanted. After going through the documentation, it looked like the template and inner components was different depending if the stepper was horizontal or vertical:&lt;/p&gt;

&lt;p&gt;For horizontal steppers, the components to use are:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;v-stepper to wrap everything.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;v-stepper-header which has to contain multiple v-stepper-step components (one for each step) and v-dividers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;v-stepper-items which has to contain one v-stepper-content for each step.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For vertical steppers, the components are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  v-stepper: to wrap everything.&lt;/li&gt;
&lt;li&gt;  v-stepper-step and v-stepper-content directly as childs of the v-stepper, one per step. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the vertical one requires less components in general and, thinking mobile-first, I decided to go for the vertical one. Next was to review the required variables to make it work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The v-stepper component requires a v-model with a numeric variable (I named it stepper) to track which content to display and the vertical property.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each v-stepper-step requires a step property with a numeric value (first one has 1, second has 2....). The complete property marks the step as complete so I'll bind it to the result of checking if the variable used in the v-stepper is bigger than its own step property:  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each v-step-content just requires a step property, similar to the one in v-stepper-step.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find a full code example in the Vuetify docs.&lt;/p&gt;

&lt;p&gt;In order to move back and forward between the steps I included buttons inside each v-step-content component that will call a function named &lt;em&gt;moveToStep(n)&lt;/em&gt; . This function receives the number of the destination step and updates the stepper variable used by the v-stepper component. In addition, this function takes care of focusing on the inputs and reseting the forms that appear in multiple steps:&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;moveToStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// actually moves to the step&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;stepper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stepNumber&lt;/span&gt;
        &lt;span class="c1"&gt;// other controls of forms&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;// Welcome step&lt;/span&gt;
              &lt;span class="c1"&gt;// reset form so it does not appear with error alert when going back to step 2&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formTaskDone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="k"&gt;break&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;// Review the Goods&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskDoneName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

              &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;// Review, the Bads&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formTaskDone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskUndoneName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

              &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;// New year Plans&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formTaskUndone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTaskTodo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

              &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;// NewYear, the word&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formTaskNewYear&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputYearWord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

              &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;// Review step&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detailsName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;In default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="k"&gt;break&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;Most of the v-step-content components contain forms with questions like the good and bad things from last year. All the questionnaire fields are bounded to variables so at the end of all the steps, I have all the user's answers stored inside the component's data object of the view. I though about saving the answers in localStorage between steps but that's wasn't really adding any value to the exercise so I discarded it at the end. The only downside is that if a user refreshes the page halfway though the exercise, he/she will lose all the answers 😞   &lt;/p&gt;

&lt;h3&gt;
  
  
  Storing the responses
&lt;/h3&gt;

&lt;p&gt;To store the user's responses I opted for a Lambda function that will receive a POST request and store the data in a Mongo database running in Mongo Atlas. This is the same approach I used to build the subscription form for our project's &lt;a href="https://thelifeboard.app"&gt;landing page&lt;/a&gt; so  if you want to see a step by step guide on how to do it, you can check &lt;a href="https://antonioufano.com/articles/create-a-serverless-subscribe-form-with-aws-lambda-function-and-api-23"&gt;this article I wrote a few weeks ago&lt;/a&gt;. In summary, the required pieces are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Mongo database (you can get one free with Mongo Atlas)&lt;/li&gt;
&lt;li&gt;  Lambda function in Node.js that receives an object with the responses, does some validations and saves it in the database using the mongodb package&lt;/li&gt;
&lt;li&gt;  An AWS API that exposes the endpoint the Vue.js application will send the request to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the tricky things when dealing with APIs is CORS as if the API is not returning the proper headers to the front end, the browser will block the requests. If you're not familiar with CORS I really recommend checking &lt;a href="https://nvisium.com/blog/2019/07/10/how-we-built-it-1.html"&gt;this article&lt;/a&gt; as it explains what it is very well. In AWS API Gateway you can enable CORS using the following option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.antonioufano.com/image_uploads/aws_cors.PNG"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LoGptt6X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.antonioufano.com/image_uploads/aws_cors.PNG" alt="Cors in AWS Gateway"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once CORS is enabled, the API will expose an OPTIONS endpoint which will return the required headers and should be reachable from our front end. &lt;/p&gt;

&lt;p&gt;To make the request to the API, I created a file named NewYearService.js which uses axios to create the object with the API details, like the URL. It also exports the function &lt;em&gt;postResolutions(resolutions)&lt;/em&gt; that receives an object with all the user's responses and makes the POST request to the API:&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="c1"&gt;// NewYearService.js file&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&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;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://my-base-url.aws.lambda.com`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;withCredentials&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="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="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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;postResolutions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolutions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/resolutions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolutions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;console&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="s2"&gt;`Posted ok! &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;data&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;return&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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;Then in the NewYear.vue view file I just have to import the NewYearService.js file and create a method that calls the &lt;em&gt;postResolutions()&lt;/em&gt; function:&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="c1"&gt;// part of NewYear.vue file&lt;/span&gt;
    &lt;span class="p"&gt;........&lt;/span&gt;
      &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;submitYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sending resolutions...&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;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="k"&gt;if&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submitYearForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;NewYearService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postResolutions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;done2019&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;listDone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;undone2019&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;listUndone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;resolutions2020&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;listNewYear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;word2020&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;yearWord&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;email&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;userMail&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="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
                &lt;span class="c1"&gt;// move to next page&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;stepper&lt;/span&gt;&lt;span class="o"&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;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;alertMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🤕 There was an error saving your data. Please try again&lt;/span&gt;&lt;span class="dl"&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;showAlert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="p"&gt;}&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&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="p"&gt;......&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending the confirmation email
&lt;/h3&gt;

&lt;p&gt;I thought about using an event system (like AWS SNS) that triggers a message every time the user's resolutions are stored in the database and then capture the events to send the emails asynchronly but that was adding a lot of complexity and I wanted to finish the exercise as soon as possible so I opted again for using a Lambda function which I'd call right after the one to store the data finished.&lt;/p&gt;

&lt;p&gt;I've done emails in previous projects and the easiest way I know to send them with Node.js is using the nodemailer package. With nodemailer you just need your email SMTP server and account details to create an email transport and send it. Find below a small example that sends an HTML email:&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;nodemailer&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;nodemailer&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;fs&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;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SMTP_SERVER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SMTP_PORT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SMTP_USER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SMTP_PASS&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="c1"&gt;// Read email html template file&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mailHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./email.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Create HTML email&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SMTP_USER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;antonio@mydomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a test email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mailHtml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;attachments&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="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mydomain.com/img/image.jpg&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;span class="c1"&gt;// Send mail&lt;/span&gt;
    &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&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="nx"&gt;info&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;I started creating the HTML template manually but soon I noticed that I was going to spend a lot of time to get a decent design that worked in multiple devices so I searched online and found &lt;a href="https://beefree.io"&gt;beefree.io&lt;/a&gt; . It has a super easy to use (and free!) drag&amp;amp;drop designer that allows you to download the HTML file so it was super handy.&lt;/p&gt;

&lt;p&gt;To trigger the Lambda that sends the email, I added a new endpoint in the API that I created to store the resolutions and then added a new function named &lt;em&gt;sendMail()&lt;/em&gt; in my NewYearService.js file. This function would receive an object with the user's email address, name or whatever I want to include in the email:&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="c1"&gt;// NewYearService.js file&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="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userDetails&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userDetails&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;console&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="s2"&gt;`Mail queued ok! &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;data&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;return&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;postResolutions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolutions&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;Then I included the call to this function right after I receive a response from the API that stores the resolutions in the database:&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="c1"&gt;//part of NewYear.vue file&lt;/span&gt;
    &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;submitYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sending resolutions...&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;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="k"&gt;if&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;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submitYearForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;NewYearService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postResolutions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;done2019&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;listDone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;undone2019&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;listUndone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;resolutions2020&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;listNewYear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;word2020&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;yearWord&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;email&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;userMail&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="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// resolutions stored in the DB :)&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;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
                &lt;span class="c1"&gt;// move to next page&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;stepper&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
                &lt;span class="c1"&gt;// Call to API to send the email&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NewYearService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMail&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;email&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;userMail&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;alertMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🤕 There was an error saving your data. Please try again&lt;/span&gt;&lt;span class="dl"&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;showAlert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="p"&gt;}&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&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="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending the email within a year
&lt;/h3&gt;

&lt;p&gt;This part is still in progress but my firs idea is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a new HTML template for the email with dynamic contentthat  I can replace with the user's data  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a cron job that runs daily&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It will query the database that contains the user's resolutions filtering by the date (when it runs the 24th of December 2020, it will filter by 24th December 2019)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each returned result, send an email containing the user's resolutions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have almost a year to do this but if you have any &lt;strong&gt;suggestions&lt;/strong&gt; on how you'll do it, they &lt;strong&gt;are more than welcome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hope you've learnt something useful from this article and, if you want to check the exercise, you can find it &lt;a href="https://thelifeboard.app/newyear"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy new year and happy coding!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://antonioufano.com"&gt;my website&lt;/a&gt;. If you like it, you may find interesting previous articles in &lt;a href="https://antonioufano.com/blog"&gt;my blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vue</category>
      <category>node</category>
      <category>aws</category>
    </item>
    <item>
      <title>Your first serverless function for a subscribe form</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Thu, 19 Dec 2019 18:27:08 +0000</pubDate>
      <link>https://dev.to/uf4no/your-first-serverless-function-for-a-subscribe-form-4m96</link>
      <guid>https://dev.to/uf4no/your-first-serverless-function-for-a-subscribe-form-4m96</guid>
      <description>&lt;p&gt;One of the best advices I've read online when starting a new product or 
service is to create a landing page to explain the problem it solves and its features, then share it online to validate if it's something
 people will be interested in.  It's never being easier to built a static site than today, with no code
 tools like Webflow or sites with templates like SquareSpace. I've never been a fan of this as I always find that when I have to modify very small details, things get tricky and sometimes are just not possible, so for my last side project I decided to use Vue CLI + &lt;a href="https://vuetifyjs.com/en/" title="Vuetify website" rel="noopener noreferrer"&gt;Vuetify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With Vue CLI I can scaffold a static site in minutes and using &lt;a href="https://vuetifyjs.com/en/components/api-explorer" title="Vuetify components" rel="noopener noreferrer"&gt;Vuetify&lt;/a&gt; I was able to leverage it's grid system and components to have a fully responsive site without the need of spending too much time creating the components, styling them etc... However when it was time to create a subscribe form I realized I was going to need something else. I didn't wanted to spin up a full back end server just to handle subscribers so I thought this was the perfect situation to use serverless functions as they come with a lot of advantages:&lt;br&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The code of your function just runs whenever it's triggered&lt;/li&gt;
&lt;li&gt;Most cloud providers have a free tier which should be more than enough for most side projects&lt;/li&gt;
&lt;li&gt;Requires less code so it's easier to maintain&lt;/li&gt;
&lt;li&gt;Faster to develop and deploy than a back end server
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As my cloud provider is AWS I used a Lambda function for my subscribe form. This is how I did it:&lt;/p&gt;
&lt;h3&gt;Creating the subscription form with Vuetify&lt;br&gt;
&lt;/h3&gt;
&lt;p&gt;With Vuetify it's super easy to create forms using the &lt;a href="https://vuetifyjs.com/en/components/forms" title="v-form component" rel="noopener noreferrer"&gt;v-form component&lt;/a&gt;. You can add text fields, selectors, text areas, date pickers... whatever you might need. In addition, adding validations for your fields just requires to setup a few rules. There are multiple examples in the Vuetify v-form site, you can see mine in &lt;a href="https://gist.github.com/uF4No/f76a42ab4e1948e26ab6e1bfef143c46" rel="noopener noreferrer"&gt;the following gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you can see the template is done using v-row and v-col to wrap everything and the form contains two v-text-field binded to the data attributes newSub.name and newSub.email . The submit button triggers the handleSubscribe function which firsts validates if the fields contain valid data and if so, calls the postSubscriber function from the SubscriberService, a file that will contain all the necessary code to trigger the AWS Lambda function. In the component data I also included the following variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;isLoading: will take care of displaying a loading animation in the submit button. I'll change it to true as soon as the button is clicked and turn it to false as soon as I receive a response from the API, sucessful or an error.&lt;/li&gt;
&lt;li&gt;showAlert: will be used to display or not a message to the user.&lt;/li&gt;
&lt;li&gt;alertType: defaulted to 'ok'. If the subscription request fails, I'll change it to 'error'. I'll use this values in different css classes in the style section of the component: alert-ok will have a green background and alert-error will have a red background.&lt;/li&gt;
&lt;li&gt;alertMessage: will contain the message for the alert.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SubscriptionService file will use axios to trigger the request to our Lambda function so you'll need to install it via npm/yarn. This is the code of the whole file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import axios from 'axios'

const apiClient = new axios.create({
  baseURL: `${process.env.VUE_APP_SUBSCRIBE_API}`,
  withCredentials: false,
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
})

export default {
  async postSubscriber(subscriberDetails) {
    try {
      const res = await apiClient.post('/subscribers', subscriberDetails)
      console.log(`Posted ok! ${res.data}`)
      return res
    } catch (err) {
      console.error(err)
      throw err
    }
  },
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, first thing it does is to import axios and then it creates the apiClient passing a configuration object to the create function of axios. Notice that I'm using an environment variable to load the baseURL which will be the endpoint of the Lambda function that will handle our request. Just remember to create this environment variable after you create you Lambda function (more on this later). In my case, I can use a .env file and Vue CLI will just load them for me when the application starts.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Then the postSubscriber function will receive an object with the subscriber details (name and email) as a parameter and it'll just send a POST request using the apiClient created above. If something goes wrong I'll write the error in console and throw it so it's handled in the catch block in the Vue component.&lt;/p&gt;

&lt;br&gt;
&lt;h3&gt;Lambda function code&lt;/h3&gt;
&lt;p&gt;I had no idea how to start with the Lambda functions so the first thing I did was create a default function from scratch using Node.js as a runtime. &lt;br&gt;&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://www.antonioufano.com/image_uploads/lambda_1.PNG" rel="noopener noreferrer"&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.antonioufano.com%2Fimage_uploads%2Flambda_1.PNG" alt="lambda functions console"&gt;
&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;By default this creates a function that returns a hello message. To test it we have to click in the Test button in the top of the page and create an event. Events are different types of requests with different input in the request body. As our current code is not doing anything with the request body, we could leave the default one and test it, but if we want to prepare the event to simulate a subscription request, we can include name and email as the request body and then just use this event to test it.&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://www.antonioufano.com/image_uploads/lambda_2.PNG" rel="noopener noreferrer"&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.antonioufano.com%2Fimage_uploads%2Flambda_2.PNG" alt="lambda functions console"&gt;
&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Now is time to modify the code of the function to actually save the user's name and email in our database. I will be using MongoDB and, in case you dont have one already, you can sign up in Mongo Atlas to get a 500Mb cluster for free. This will be more than enough to store data for a few side projects so it's a really good option. You can find a &lt;a href="https://docs.atlas.mongodb.com/getting-started/" title="Mongo Atlas guide" rel="noopener noreferrer"&gt;step by step guide in their docs&lt;/a&gt;.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;If you're hosting your database somewhere else, you'll need the host, user and password to connect to it.&lt;/p&gt;
&lt;p&gt;As the code to store the data in the database is more complex and requires the mongodb dependency we'll use a code editor locally instead of the one in the Lambda console. The first thing we'll do is to create a folder to store all our code, then navigate to it in a terminal and run '&lt;em&gt;npm install mongodb&lt;/em&gt;' to install the mongodb dependency that we'll use to connect and interact with our database. Once the mongodb dependency is installed we can create the Javascript file that will contain all our logic to connect and interact with our database. In my case, I named it subscribe.js.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Notice that we didnt need to run 'npm init' to generate a package.json file as we'll not need to run npm install after deploying our code. Instead we'll upload our code and the node_modules folder to AWS. More on this later.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After searching online for a few tutorials and tips, I found &lt;a href="https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas" title="guide to Mongodb and lambda" rel="noopener noreferrer"&gt;this article&lt;/a&gt; about how to create the database connection and even some optimizations for lambda, like caching the database connection to increase performance. From this article I was able to extract a code example that splitted all the logic in four different functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;connectToDatabase: async function that receives the database connection string and returns an opened connection&lt;/li&gt;
&lt;li&gt;queryDatabase: async function that receives the database connection and the Lambda event (which has the request body). In our case this will have the name and email of the subscriber
&lt;/li&gt;
&lt;li&gt;processEvent: is a wrapper that will call the connectToDatabase and the queryDatabase functions. As these are async functions it will call them using await.&lt;/li&gt;
&lt;li&gt;handler: this is the default function that is exported and receives as params the event and the context object.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code from the article uses callbacks but it also contains a link to an &lt;a href="https://github.com/rlondner/aws-stepfunctions-samples/blob/master/restaurants/index.js" title="guide to Mongodb and lambda with async/await" rel="noopener noreferrer"&gt;example in GitHub which uses asyn/await instead&lt;/a&gt;. Let's review the functions one by one:&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;async connectToDatabase(uri)&lt;/em&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
const MongoClient = require('mongodb').MongoClient

//Performance optimization Step 1: declare the database connection object outside 
//the handler method so it's cached
let cachedDb = null

async function connectToDatabase(uri) {
  try {
    //Performance optimization Step 3: test that database connection exists 
    // and is valid before re-using it
    if (cachedDb &amp;amp;&amp;amp; cachedDb.serverConfig.isConnected()) {
      console.log('=&amp;gt; using cached database instance');
      return cachedDb
    }
    const dbName = 'MY_DATABASE';
    const client = await MongoClient.connect(uri)
    cachedDb = client.db(dbName)
    return cachedDb
  } catch (error) {
    console.log(error) 
    return error
  }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the variable cachedDb in the main context (not inside any of the functions) and the first thing we do is to check if it already exists and if it's connected. If it already exists we return it and if not, we connect to the server and database and assign it to cachedDb before returning. By declaring cachedDb in the main context we allow AWS Lambda to keep the database connection open for some time and it can be reusable for different executions of our function. This is explained in &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/running-lambda-code.html" title="AWS lambda context official docs" rel="noopener noreferrer"&gt;this section from the official AWS Lambda docs&lt;/a&gt; which mentions: &lt;/p&gt;


&lt;blockquote&gt;&lt;em&gt;Objects declared outside of the function's handler method remain initialized, providing additional optimization when the function is invoked again. For example, if your Lambda function establishes a database connection, instead of reestablishing the connection, the original connection is used in subsequent invocations. We suggest adding logic in your code to check if a connection exists before creating one.&lt;/em&gt;&lt;/blockquote&gt;

&lt;br&gt;

&lt;h4&gt;&lt;em&gt;async queryDatabase(db, event)&lt;/em&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;

async function queryDatabase(db, event) {
  let response = {
    isBase64Encoded: false,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  }
  try {
    var jsonContents = JSON.parse(JSON.stringify(event))

    //handling API Gateway input where the event is embedded into the 'body' element
    if (!event.body !== null &amp;amp;&amp;amp; !event.body !== undefined) {
      response.statusCode = 420
      response.body = JSON.stringify({
        message: 'Invalid input'
      })
      return response
    }
    console.log('retrieving payload from event.body')
    jsonContents = JSON.parse(event.body)

    if (!jsonContents.name &amp;amp;&amp;amp; !jsonContents.email) {
      response.statusCode = 420
      response.body = JSON.stringify({
        message: 'Missing params in request body'
      })
      return response
    }
    const now = new Date()

    const dbResponse = await db.collection('Subscribers').insertOne({
      name: jsonContents.name,
      email: jsonContents.email,
      createdAt: now,
      updatedAt: now,
      __v: 0
    })
    console.log('New Sub inserted: ', dbResponse)
    response = {
      statusCode: 201,
      body: JSON.stringify({
        message: 'Subscribed ok'
      })
    }

    return response
  } catch (error) {
    console.log(error)
    return error
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function receives the database connection object and the AWS Lambda event. First thing it does is to check if the event contains a valid body and, if not, return the response object with status code 420. If the event has a body, it parses it to JSON with and then checks if it contains a name and email properties. Again, if it doesn't it will return the response with a 420 status code. Lastly, if both validations are passed, it will insert the record in the 'Subscribers' collection and return a reponse with a 201 status code.&lt;/p&gt;
&lt;p&gt;A few things to keep in mind are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Access-Control-Allow-Origin header: Make sure you configure it accordingly to your website domain.&lt;/li&gt;
&lt;li&gt;Validations: in this example I'm not validating if the email has a valid format or if the name contain just letters. Although these validations are done in the form in the website, it's important to include them in the back end as well.&lt;/li&gt;
&lt;li&gt;The response body has to be a JSON stringifyed.
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;em&gt;async processEvent(event)&lt;/em&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;


async function processEvent(event) {
  try {
    const db = await connectToDatabase(atlas_connection_uri)
    const result = await queryDatabase(db, event)

    console.log('query results: ', result)

    return result
  } catch (err) {
    console.log('Error processing event: ', err)
    return err
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The processEvent function is just responsible of calling the connectToDatabase and queryDatabase functions using await.&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;handler(event, context)&lt;/em&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;

exports.handler = async (event, context) =&amp;gt; {
  try {
    var uri = process.env['MONGODB_ATLAS_CLUSTER_URI']

    console.log('remaining time =', context.getRemainingTimeInMillis())
    console.log('functionName =', context.functionName)
    console.log('AWSrequestID =', context.awsRequestId)
    console.log('logGroupName =', context.logGroupName)
    console.log('logStreamName =', context.logStreamName)
    console.log('clientContext =', context.clientContext)

    //Performance optimization Step 2: set context.callbackWaitsForEmptyEventLoop to false
    //to prevent the Lambda function from waiting for all resources (such as the database connection) to be released before returning it
    context.callbackWaitsForEmptyEventLoop = false

    if (atlas_connection_uri == null) {
      atlas_connection_uri = uri
      /*
      const kms = new AWS.KMS();
      kms.decrypt({ CiphertextBlob: new Buffer(uri, 'base64') }, (err, data) =&amp;gt; {
        if (err) {
            console.log('Decrypt error:', err);
            return callback(err);
        }
        
        atlas_connection_uri = data.Plaintext.toString('ascii');
      }) 
      */
    }
    const res = await processEvent(event)
    console.log('Handler response is: ', res)
    return res
  } catch (error) {
    console.log(error)
    return error
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The handler function is the one we'll export and it's actually the one that will handle the request as it's the one we'll put as handler in the AWS Lambda console. It will receive the event (which contains the body of the request) and a context object. The context contains basic info like a unique identifier, the remaining time we have to execute our code etc... This function has two responsabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load the database connection string, from and environment variable or, preferibly, from an AWS Secret. This requires us to install the aws-sdk package.
&lt;/li&gt;
&lt;li&gt;call the processEvent function&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The file with all the code would be similar to the one in &lt;a href="https://gist.github.com/uF4No/61b9eeac2fc99f80de4b733a8375e0b8" title="lambda function mongo" rel="noopener noreferrer"&gt;this gist&lt;/a&gt;&lt;strong&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In order to upload our code to AWS, we have to create a .zip file containing both our index.js file and the node_modules folder. Then go to the AWS Lambda console and in the Code Entry Type dropdown, select Upload a .zip file. Once uploaded, make sure the runtime is still Node.js and that the Handler matches with you index filename and the exported function, in my case 'index.handler'. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;In addition, if you're using an environment variable to load your database connection string (as in the example above), remember to add it in the Environment Variables section in the Lambda console. Keep in mind that for this type of sensitive data, it's recommended to use something more secure, like the KMS (Key Management Service), for which there youu can adapt the commented code in handler function.&lt;/p&gt;
&lt;p&gt; Now we can test it using the Test button in the top of the screen. We can event create valid events (with name and email) and invalid ones to check if the validations we included in the queryDatabase function are working as expected.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Once we have tested our code using the Lambda console we need a way to trigger it from the outside world and for that we're going to need an API. Luckily for us the AWS API Gateway is going to simplify this task for us.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;API Creation in AWS API Gateway&lt;/h3&gt;
&lt;p&gt;To start creating our API we have to go to the Designer section in the Lambda console, and find the Add trigger button.  In the next screen select API Gateway in Trigger Configuration, choose Create new API and REST API as the template. In the Additional settings you can change the API name to whatever you want, leave Deployment stage as default and metrics and error logging disabled for now:&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://www.antonioufano.com/image_uploads/API_1.PNG" rel="noopener noreferrer"&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.antonioufano.com%2Fimage_uploads%2FAPI_1.PNG" alt="lambda functions console"&gt;
&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Once done, click the Add button and you new API will be created. Back in the Lambda console you should see the API Gateway as a trigger in the Designer section and if you click on it, you'll see the details and the endpoint of your API. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;To test it you can copy the API endpoint and send a request using Postman/Curl. This should work out of the box but if you noticed, by default our endpoint accepts any method (GET, POST, PUT...) and ideally we'll be just listening for POST requests. To fix this, go to the API Gateway service in the AWS console and you should see your API, click on it to see its details. As you can see it has a single endpoint (named /test by default) with "ANY" method. With the Actions button, click on Create method and add the OPTIONS and POST methods. For the OPTIONS one, you'll need to select the Mock integration type and save it. This will make it return a 200 by default withouth actually calling any Lambda function or other code. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: we'll need the OPTIONS method as this will be triggered by the browser before actually sending a POST request.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the POST method we'll select Lambda function as Integration Type, select the Use Lambda Proxy integration and then enter the name of our function in the Lambda function field. We'll get a popup with the message "You're about to give API Gateway permission to invoke your Lambda function", so click Ok. Now we can actually remove the ANY method using the Actions button as we'll be using only POST/OPTIONS requests. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;Once done, you'll have to re add the trigger for your function in the Lambda Designer section and you should be ready to go.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;br&gt;
&lt;/h3&gt;
&lt;p&gt;I think serverless functions can be very useful when starting a new project or even to handle all the backend in small applications. In my case, I plan to use them for small isolated tasks in my side projects like this subscription example. In addition I plan to start writting them in other programming languages like Python and Go as it could be a nice way to start learning them 😄&lt;/p&gt;
&lt;p&gt;Hope this helps you start playing with serverless functions&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;br&gt;&lt;/p&gt;



&lt;br&gt;
&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://www.antonioufano.com" rel="noopener noreferrer"&gt;my website&lt;/a&gt;. If you like it, you may find interesting previous articles in &lt;a href="https://www.antonioufano.com/articles" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;. In addition, I'm working on a side project called the LifeBoard, an app to help people identify and achive their goals. If that sounds interesting, &lt;a href="https://thelifeboard.app" rel="noopener noreferrer"&gt;check out the landing page&lt;/a&gt; and give me some feedback &lt;a href="https://twitter.com/uf4no" rel="noopener noreferrer"&gt;in Twitter&lt;/a&gt; or subscribe if you want to receive updates 😉&lt;/em&gt;&lt;/p&gt;


</description>
      <category>beginners</category>
      <category>serverless</category>
      <category>vue</category>
    </item>
    <item>
      <title>Understanding the basics of Socket.io</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Thu, 05 Dec 2019 17:41:39 +0000</pubDate>
      <link>https://dev.to/uf4no/understanding-the-basics-of-socket-io-3a0e</link>
      <guid>https://dev.to/uf4no/understanding-the-basics-of-socket-io-3a0e</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://www.antonioufano.com" rel="noopener noreferrer"&gt;my website&lt;/a&gt;. If you like it, you may find interesting previous articles in &lt;a href="https://www.antonioufano.com/blog" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;After doing a lot of APIs and microservices lately, I've been trying to find new ideas for quick demos to improve my skills. In one of the projects I'm working on, I'm planning to include a feed that is refreshed with the activity of the users in real time. I wasn's sure about how to do it and, at first I thought about using RabbitMQ, but after a quick search I found WebSockets and &lt;a href="https://socket.io/" title="socket.io page" rel="noopener noreferrer"&gt;Socket.io&lt;/a&gt;. If you want to learnt what WebSockets are, watch &lt;a href="https://www.youtube.com/watch?v=ZbrEztkwcw8" title="websockets explained" rel="noopener noreferrer"&gt;this super quick video&lt;/a&gt; to understand the basic concepts. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;Instead of builing directly the user's feed for my project, I decided to build a quick chat demo first. There are multiple articles and videos that explain how to create a chat
 with socket.io but most of them don't explain exactly how all involved parts 
work together or are just a small demo to run locally but 
is not "deployable" to production. So I took all those examples as references to build my chat, took notes of everything that wasnt clear for me and built it in a way so it can be deployed to a server (even created a Docker image!). Here are all my notes.&lt;/p&gt;
&lt;h3&gt;Chat server and client responsabilities&lt;br&gt;
&lt;/h3&gt;
&lt;p&gt;Our chat app server will have following responsabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serve the HTML/CSS/JS client files to the users&lt;/li&gt;
&lt;li&gt;Start Socket.io connection&lt;/li&gt;
&lt;li&gt;Serve socket.io library to the clients (optional as clients can also load it from a CDN)&lt;/li&gt;
&lt;li&gt;Broadcast events (like a new chat message) to all clients connected&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a user connects to our server from his browser, he'll receive the HTML/CSS/JS client files which will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load socket.io client library (from our server or from a CDN)&lt;/li&gt;
&lt;li&gt;Stablish connection with the Socket.io running in our server&lt;/li&gt;
&lt;li&gt;Ask user to enter his name so we can identify him in the chat
&lt;/li&gt;
&lt;li&gt;Emit and receive events to/from Socket.io running in our server&lt;/li&gt;
&lt;li&gt;Add our own messages to the chat via JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;&lt;h3&gt;Chat server in detail&lt;/h3&gt;
&lt;p&gt;First thing is to start our Node.js project with &lt;em&gt;"npm init"&lt;/em&gt; as we'd have to install dependencies later. We can use Node's http module to create a static server that sends our client any type of files, in our case it would be html, css and js. I found &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Node_server_without_framework" title="node server mozilla docs" rel="noopener noreferrer"&gt;this example&lt;/a&gt; in the Mozilla docs which was exactly what I was looking for. No framework, just an http server able to send html, css, js, images and more. They also explain how it works line by line so I'll not go into that. I put the server code in a file named server.js. The only things I changed from the Mozilla example are the port number and the path were it reads the files from, as I'll use a folder named "client":&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var filePath = './client' + request.url;
console.log(filePath)
if (filePath == './client/') {
  filePath = './client/index.html';
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;p&gt;Next step was to install the socket.io dependency with "&lt;em&gt;npm i socket.io&lt;/em&gt;" include it in our server.js file and log something when we detect a connection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var io = require('socket.io')(app);
// starts socket
io.on('connection', function (socket) {
  console.log('Socket.io started.....')
  // Manage all socket.io events next...
    socket.on('new-connection', (data) =&amp;gt; {
    // captures event when new clients join
    console.log(`new-connection event received`)
    .........
  })
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I've also included how to capture an event called 'new-connection', which for now, will just print something in the console. Now let's move to the client.&lt;/p&gt;

&lt;h3&gt;Chat client in detail&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, I placed all our client files (html, css and js) in a folder named &lt;em&gt;client&lt;/em&gt;. The index.html file is pretty simple: &lt;br&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in the header we're loading the socket.io client library, from a CDN although I've also included the code to load it from our own server&lt;/li&gt;
&lt;li&gt;also in the header, we load our script.js file.&lt;/li&gt;
&lt;li&gt;the body only contains a div container for all the chat messages and a form to submit new ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the code of the index.html file in &lt;a href="https://gist.github.com/uF4No/5303f07fab1223690d353addcacdcdf7" rel="noopener noreferrer"&gt;this GitHub Gist&lt;/a&gt; or directly in &lt;a href="https://github.com/uF4No/chat-socket.io" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;.&lt;/p&gt; 



&lt;p&gt;In the client &lt;em&gt;script.js&lt;/em&gt; file, the first thing I did was to connect via socket.io from the client to the server. As I'm loading the socket.io library before the script.js file, I have it available so I can use the &lt;strong&gt;&lt;em&gt;io() &lt;/em&gt;&lt;/strong&gt; function to create a socket connected to the server and the &lt;strong&gt;&lt;em&gt;emit()&lt;/em&gt; function to send a basic event&lt;/strong&gt; named &lt;em&gt;'new-connection'&lt;/em&gt; and the name of the user:&lt;br&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Use io (loaded earlier) to connect with the socket instance running in your server. 
 * IMPORTANT! By default, socket.io() connects to the host that 
 * served the page, so we dont have to pass the server url
 */
var socket = io();

//prompt to ask user's name 
const name = prompt('Welcome! Please enter your name:')

// emit event to server with the user's name
socket.emit('new-connection', {username: name})

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, if I start the server using '&lt;em&gt;node server.js&lt;/em&gt;' and open the browser I get the prompt and after entering the name, I'll be connected to the socket server and see something like this in the server console:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$ npm start

&amp;gt; chatsocket.io@1.0.0 start /d/Projects/chatSocket.io
&amp;gt; node server.js

HTTP Server running at http://127.0.0.1:3000/
request  /
./client/
request  /script.js
./client/script.js 
request  /style.css
./client/style.css
Socket.io started.....
request  /favicon.ico
./client/favicon.ico

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Up to this point, I was able to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;start a static server that sends the client files and opens the socket.io connection&lt;/li&gt;
&lt;li&gt;connect the clients to the server via socket.io and emit an event called 'new-connection'&lt;/li&gt;
&lt;li&gt;capture the 'new-connection' event in the server and print it to the console&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The only things missing to complete the chat application were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;being able to link messages with the user's names
&lt;/li&gt;
&lt;li&gt;add messages we send to the chat-container div
&lt;/li&gt;
&lt;li&gt;emit event to the server containing the message sent
&lt;/li&gt;
&lt;li&gt;broadcast chat messages received in the server to all clients connected
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding the messages we send to the chat-container div is something we can do in the client &lt;em&gt;script.js&lt;/em&gt; file. We just need to add an event listener to capture when the form is submitted and, whenever that happens, create a new div containing the message inside the chat-container. As this is something I'll have to do also when we receive messages from other users, I created a function called &lt;em&gt;addMessage(data, type) &lt;/em&gt;which I can call multiple times. In addition, I trigger an event called 'new-message' sending to the server an object with both the message and the client's socket id.&lt;br&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// get elements of our html page
const chatContainer = document.getElementById('chat-container')
const messageInput = document.getElementById('messageInput')
const messageForm = document.getElementById('messageForm')

messageForm.addEventListener('submit', (e) =&amp;gt; {
  // avoid submit the form and refresh the page
  e.preventDefault()
  // check if there is a message in the input
  if(messageInput.value !== ''){
    let newMessage = messageInput.value
    //sends message and our id to socket server
    socket.emit('new-message', {user: socket.id, message: newMessage})
    addMessage({message: newMessage}, 'my' )
    //resets input
    messageInput.value = ''
  }
})

// receives two params, the message and if it was sent by you
// so we can style them differently
function addMessage(data, type){
  const messageElement = document.createElement('div')
  messageElement.classList.add('message')

  if(type === 'my'){
    messageElement.classList.add('my-message')
    messageElement.innerText = `${data.message}`

  }else if(type === 'others'){
    messageElement.classList.add('others-message')
    messageElement.innerText = `${data.user}: ${data.message}`

  }else{
    messageElement.innerText = `${data.message}`

  }
  // adds the new div to the message container div
  chatContainer.append(messageElement)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that I also added different styles to the messages depending if they belong to the user or if they are received from others.&lt;/p&gt;
&lt;p&gt;Next step is to handle the 'new-connection' and 'new-message' events properly in our &lt;em&gt;server.js&lt;/em&gt;. In the 'new-connection' event I stored the client's socket id and the user name as key:values of an object named&lt;em&gt; users&lt;/em&gt;. Then in the 'new-message' event, I used the socket id received to find the correspondent user name, and with the &lt;strong&gt;&lt;em&gt;broadcast()&lt;/em&gt; function, send the message information to all clients connected except the one that emitted the event originally&lt;/strong&gt;.&lt;em&gt;&lt;br&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// we'll store the users in this object as socketId: username
const users = {}

var io = require('socket.io')(app);
// starts socket
io.on('connection', function (socket) {
  console.log('Socket.io started.....')
  // Manage all socket.io events next...
  socket.on('new-connection', (data) =&amp;gt; {
    console.log(`new-connection event ${data.username}`)
    // adds user to list
    users[socket.id] = data.username
    socket.emit('welcome', { user: data.username, message: `Welcome to this Socket.io chat ${data.username}` });
  })
  socket.on('new-message', (data) =&amp;gt; {
    console.log(`new-message event ${data}`);
    // broadcast message to all sockets except the one that triggered the event
    socket.broadcast.emit('broadcast-message', {user: users[data.user], message: data.message})
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With these last few bits done, I had a fully functional chat application and I could test it by opening multiple browsers locally: &lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.antonioufano.com/image_uploads/chats.PNG" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.antonioufano.com%2Fimage_uploads%2Fchats.PNG" alt="chat app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only thing I havent covered up until now is the styling (which you can find in the &lt;a href="https://github.com/uF4No/chat-socket.io/blob/master/client/style.css" title="styles file" rel="noopener noreferrer"&gt;style.css file&lt;/a&gt;) and minor validations, like making sure users cannot send empty messages. You can find the full code in &lt;a href="https://github.com/uF4No/chat-socket.io" title="code repo" rel="noopener noreferrer"&gt;this repo in GitHub&lt;/a&gt;. It also contains a Dockerfile so you can build an image and deploy it anywhere with Docker 🙃 or if you just want to try it online, visit &lt;a href="http://chatsocket-demo.eu-west-1.elasticbeanstalk.com/" rel="noopener noreferrer"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For this app I just needed to use the emit() and broadcast() functions but Socket.io contains a lot more functionalities like assign namespaces to sockets so they have different endpoints, create rooms and even integrate it with Redis. You can find examples of all those in &lt;a href="https://socket.io/docs/" title="socket.io documentation" rel="noopener noreferrer"&gt;the docs&lt;/a&gt;. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;Hope this helps you understand WebSockets and how Socket.io works.&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;br&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Vue London 2019 conference recap (slides and repos)</title>
      <dc:creator>Antonio</dc:creator>
      <pubDate>Tue, 22 Oct 2019 13:30:59 +0000</pubDate>
      <link>https://dev.to/uf4no/vue-london-2019-conference-recap-slides-and-repos-ckm</link>
      <guid>https://dev.to/uf4no/vue-london-2019-conference-recap-slides-and-repos-ckm</guid>
      <description>&lt;p&gt;On October 4 took place one of the biggest Vue.js conferences in Europe, &lt;a href="https://www.vuejs.london"&gt;Vue.london&lt;/a&gt;. As soon as I heard about it I marked it in my calendar and bought an early bird ticket. At around £250 it's not a cheap conference but given the quality of the talks, the venue, how well it was organized and all the swag (t-shirts, bags, stickers, discounts...), I think it was worth the price.&lt;/p&gt;

&lt;h3&gt;The Venue&lt;/h3&gt;

&lt;p&gt;Talking about the venue, it was in the CineWorld the O2 peninsula so the screen was suuuper big. Breakfast was served before the first talk and there were multiples options for lunch (the dim sum were super good...), coffee, tea, cakes etc...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.antonioufano.com/image_uploads/doors.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z3aCY9Lp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.antonioufano.com/image_uploads/doors.jpg" alt="Vue London 2019 entrance" width="50%"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the lounge you could also find the booths of the sponsors: Cloudinary, Cypress.io, KendoUI, Attest, VueMastery and SamKnows (I'm probably missing a few more). There was also an advice lounge where you could ask for help or just talk with experts, the speakers or organisers, and there was even a gaming room so there was plenty of things to do during the breaks.&lt;/p&gt;

&lt;h3&gt;The talks&lt;/h3&gt;

&lt;p&gt;
The first talk was by &lt;a href="https://www.twitter.com/adamjahr"&gt;Adam Jahr&lt;/a&gt;, from &lt;a href="https://www.vuemastery.com"&gt;VueMastery&lt;/a&gt; and it was an introduction to animations in Vue explaining how we can use animations to drive focus and then showed us how to use the transition element and combined with CSS transtions. As all the content they do in VueMastery, it was super easy to follow and it was part of their &lt;a href="https://www.vuemastery.com/courses/animating-vue/why-animate"&gt;Animating Vue course&lt;/a&gt; so if you want to know more about the topic, you can sign up to do the full course.
&lt;/p&gt;

&lt;p&gt;Adam's talk was followed by &lt;a href="https://www.twitter.com/mayashavin"&gt;Maya Shavin&lt;/a&gt; with "Scripting in Style, what's your Vue" who gave us a history lesson on the evolution of the Web and analised the pros and cons of the different ways to apply styling to our components: global style,scoped style and CSS in JS.&lt;a href="https://slides.com/mayashavin/styling-with-vue/fullscreen"&gt; Slides&lt;/a&gt; and &lt;a href="https://github.com/mayashavin/style-with-vue-demo"&gt;Repository&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Next was &lt;a href="https://www.twitter.com/filrakowski"&gt;Filip Rakowski&lt;/a&gt; who explained some tricks to improve performance of our Vue.js apps like: 
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; Using dependencies that can be tree shaken (lodash-es instead of lodash!) and using &lt;a href="https://bundlephobia.com"&gt;Bundlephobia&lt;/a&gt; to quickly identify the biggest dependencies in our bundle&lt;/li&gt; 
&lt;li&gt;Lazy-loading views and components by using functions instead of regular imports&lt;/li&gt; 
&lt;li&gt;Using prefetch to load parts of our web when the browser is in idle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find &lt;a href="https://slides.com/filiprakowski/deck#/10"&gt;the slides here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twitter.com/CodesOfRa"&gt;Ramona Biscoveanu&lt;/a&gt; showed us how to use the D3 library to do data visualizations in Vue.js and how we can leverage the reactivity of the computed properties in Vue to make the data visualizations come to live. I've never used D3 but looks like it's a super powerful library for when you need something more than chartjs or similar.&lt;a href="https://slides.com/codesofra/data-visualization-in-2#/"&gt; Slides&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Vue.js core team member &lt;a href="https://www.twitter.com/posva"&gt;Eduardo San Martin Morote&lt;/a&gt; followed with an deep explanation of the Vue Router, common issues developers may find with it, like the importance of the order you declare your routes and its limitations. In addition he gave us some insights on how it will evolve in future versions and how they will separate responsabilities between the History and the Router. You can find the slides &lt;a href="https://slides.com/posva/a-new-router-to-guide-your-apps#/"&gt;here&lt;/a&gt;.

&lt;/p&gt;
&lt;p&gt;
Just before the lunch break, they did the Open Source Awards, where they recogniced some nice open source projects. The categories and nominees were:
&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Breaker of chains: Proppy, Portal Vue, Virtual Scroller and Grindsome.&lt;/li&gt;
  &lt;li&gt;Fun project: Vuera (winner), Vue Observe Visibility and Vue Funnel Graph.&lt;/li&gt;
  &lt;li&gt;Impactful Contributor: Vue Community, Tailwind CSS, Vue Storefront and TipTap (winner).&lt;/li&gt;
  &lt;li&gt;Developer Experience: Inkline, Vue Styleguidist (winner), Cion Design System and Vuese.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right after lunch, we started with a few lightning talks (around 10mins each):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;a href="https://github.com/mlama007/FocusManagement"&gt;Accesibility and Focus Management&lt;/a&gt; by &lt;a href="https://www.twitter.com/marialamardo"&gt;Maria Lamardo&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://speakerdeck.com/csilk/then-with-cypress"&gt;Hot to get your product owner to write your functional tests with Cypress.io &lt;/a&gt;. Cant remember the speaker's name but &lt;a href="https://www.twitter.com/amirrustam"&gt;Amir Rustamzadeh&lt;/a&gt; gave us a great intro to Cypress at their booth so if you're interested on it, follow him on Twitter.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://awesomejs.dev"&gt;AwesomeJS.dev&lt;/a&gt; by &lt;a href="https://www.twitter.com/akryum"&gt;Guillaume Chau&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the talks focused on the new composition API that will be added in Vue 3. &lt;a href="https://www.twitter.com/greggpollack"&gt;Gregg Pollack&lt;/a&gt; from VueMastery started with "Vue 3's Composititon API Explained Visually". First he listed the limitations of the current API and how the new API will solve them and when to use it, mainly:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;When we want Typescript support&lt;/li&gt;
  &lt;li&gt;When we have code we want to reuse in multiple components (composition functions to the rescue).&lt;/li&gt;
  &lt;li&gt;When we have components that are too big, as we would be able to extract most of the code to composition functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The talk covered the first few lessons of their Vue 3 Essentials course so if you are interested in it you can sign up to access the full course. He also presented their Vue 3 cheatsheet that you can download &lt;a href="https://www.vuemastery.com/vue-3-cheat-sheet/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/Linus_Borg"&gt;Thorsten Luenborg&lt;/a&gt; followed with a more in depth analysis of the new API methods like setup, refs, toRefs and dynamic lifecycle methods. This talk contained a lot of code examples and you can find them in &lt;a href="https://github.com/LinusBorg/talks/tree/master/2019-10-04%20Vuejs%20London"&gt;the following repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.antonioufano.com/image_uploads/vueApi.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rhe_UrAa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.antonioufano.com/image_uploads/vueApi.jpg" alt="Vue London 2019 entrance" width="50%"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a long coffee break to digest all this info, &lt;a href="https://www.twitter.com/ycmjason"&gt;Jason Yu&lt;/a&gt; did a live coding session builing a piano-computer-keyboard app using an audio API from the browser (with a song included). Definately one of the highlights of the day. His code is in &lt;a href="https://github.com/ycmjason-talks/2019-10-04-vuejs-london-conference-2019"&gt;this repo&lt;/a&gt;. He has also edited a 20min video you can find in &lt;a href="https://www.youtube.com/watch?v=JON6X6Wmteo&amp;amp;feature=youtu.be"&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To close the event, we had an online conference talk with the man himself &lt;a href="https://www.twitter.com/youyuxi"&gt;Evan You&lt;/a&gt; in which he open sourced the code of the Vue 3 &lt;a href="http://github.com//vuejs/vue-next"&gt;pre-alpha version&lt;/a&gt;. In addition, he explained the improvements done in the compiler to ignore nodes of the DOM that doesnt have reactive data, which translates in at least 6x performance improvement, and the new Suspense element that can be used to display placeholder content while an async request is done (bye bye loading = true/false in each API call).&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;I think Vue London 2019 was a big success. A well organized conference with recognized speakers, high quality talks, good sponsors, nice food and, most importantly, the mood in general was super good and welcoming. I'll try to repeat next year.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.antonioufano.com/image_uploads/scenario2.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cwYL-dvg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.antonioufano.com/image_uploads/scenario2.jpg" alt="Vue London 2019 scenario" width="50%"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PS: The organisers told me they will release the videos of the talks so I'll update the article once they are available.&lt;/p&gt;



&lt;br&gt;
&lt;p&gt;&lt;em&gt;This article was originally posted in &lt;a href="https://www.antonioufano.com"&gt;my website&lt;/a&gt;. If you like it, you may find interesting previous articles in &lt;a href="https://www.antonioufano.com/articles"&gt;my blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


</description>
      <category>vue</category>
      <category>javascript</category>
      <category>techtalks</category>
    </item>
  </channel>
</rss>
