<?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: Rachel</title>
    <description>The latest articles on DEV Community by Rachel (@rachel_cheuk).</description>
    <link>https://dev.to/rachel_cheuk</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%2F108002%2F861e480e-b177-40de-92c1-cc0eb26dc652.jpeg</url>
      <title>DEV Community: Rachel</title>
      <link>https://dev.to/rachel_cheuk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rachel_cheuk"/>
    <language>en</language>
    <item>
      <title>The Art of Vibe-Coding (with Google AI Studio): Personal Writing Assistant App</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 06 Jul 2025 01:49:20 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/the-art-of-vibe-coding-with-google-ai-studio-personal-writing-assistant-app-53ge</link>
      <guid>https://dev.to/rachel_cheuk/the-art-of-vibe-coding-with-google-ai-studio-personal-writing-assistant-app-53ge</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is my submission for &lt;a href="https://dev.to/deved/build-apps-with-google-ai-studio"&gt;DEV Education Track: Build Apps with Google AI Studio&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A (long) while back, I had this idea for a startup. I wanted to build a personal writing assistant to help fiction writers. This was in the days before Generative AI. Once Generative AI came about, I knew this idea would evolve. I documented this concept on a &lt;a href="https://unruffled-hermann-e8c0f5.netlify.app/" rel="noopener noreferrer"&gt;product page&lt;/a&gt; I developed years ago. It was time to take AI for a spin and see what it would come up with.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience
&lt;/h2&gt;

&lt;p&gt;My first thought was to give it a webpage with the product features and see what it would do. I was a bit skeptical it would work, and...it didn't. It created something completely unrelated to the product page I created. &lt;/p&gt;

&lt;p&gt;So, I would need to start simpler. Before starting this project, I had already tried creating a character generator with Google AI Studio. It was quite successful in doing that, and in under 30 minutes, I had a character generator prototype. &lt;/p&gt;

&lt;p&gt;It was a great start, but to get the "writing assistant" I envisioned, I would need to start from a different MVP. So I began crafting.&lt;/p&gt;

&lt;p&gt;My initial prompt generated a React App. I haven't used React in a while (Vue.js please!), but this seemed like a good opportunity to re-acquaint myself with React. So I continued the vibe-coding session in React. &lt;/p&gt;

&lt;h3&gt;
  
  
  Rough Outline of Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create an Editor with Gemini&lt;/strong&gt; to help generate what comes next in a sidebar. I had to specifically prompt it to bring in a RTF editor. Otherwise it defaulted to a textarea.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add tabs to the sidebar&lt;/strong&gt; with the following: Characters, Events, Locations, Timeline, Notes, and Organizations. Gemini created these tabs with Characters and Notes having auto-creation features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement Characters Tab.&lt;/strong&gt; Gemini did a great job in automatically generating a form with different fields for the character, and allowing automatic creation with Gemini.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement Organizations Tab.&lt;/strong&gt; Similar to Characters, Gemini did a great job in flushing out the Organizations tab with similar manual input and auto-generation features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement Locations Tab.&lt;/strong&gt; Similar to Characters and Organizations, Gemini did a great job in flushing out the Locations creation with relevant fields and auto-generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove Events Tab and create Events within the Timeline Tabs.&lt;/strong&gt; This was fairly straightforward and handled with ease.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add draggable interface for Events&lt;/strong&gt; in the Timeline Tab. This gave it problems due to dependency issues. These problems would persist through the end of my Vibe-coding session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Focus mode.&lt;/strong&gt; This was done with ease, but later disappeared after adding another feature. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Dark mode.&lt;/strong&gt; This was also done with ease.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a timer&lt;/strong&gt; to track writing sessions. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrate to Next.js framework.&lt;/strong&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Is this production ready? No. But it is a &lt;em&gt;great&lt;/em&gt; prototype. Would I deploy this? Personally, not as is. It's not really a complete MVP for me. There is definitely more customization I would like to do.&lt;/p&gt;

&lt;p&gt;After the Next.js migration, I downloaded it, but it did not run and there were some conflicts to resolve. I decided to revert to an older code checkpoint to capture the demo video. The migration to Next.js will come later.&lt;/p&gt;

&lt;p&gt;As the codebase grew, it became increasingly difficult to add new features. Thus, where I ended -- I think it was a good "stopping point". I may continue to ask AI for assistance, but not in the same manner going forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges with Vibe Coding
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Inevitably, just as when humans code, there are bugs. It seems the bugs are more dependency related though due to different versions loading in the browser at the same time. These bugs may be less common when developing locally. &lt;/li&gt;
&lt;li&gt;Unsurprisingly, as the codebase grows, it becomes increasingly difficult to add new features using AI. I also lost features I previously built out. I had to ask Gemini to re-add the feature.&lt;/li&gt;
&lt;li&gt;It will come up with suggestions on what it plans to do, or even imply it did something, when nothing changed. I had to tell it to "implement this" multiple times in order to get the files updated.&lt;/li&gt;
&lt;li&gt;Eventually, you'll reach a point where it loses the "fast edge" and development slows down. By the end of step 10, I kept hitting dependency errors over and over again. It was time to move development offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;p&gt;Google AI Studio is great for getting a minimalist MVP together. It's less great for taking something to production. Yes, it can be deployed via Cloud Run, but the project I built would require more invisible features than the MVP generated. It was a great starting point for hooking up the main features I wanted. I will need to do the "hard part" afterwards. AI isn't there yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/usQ-H1R9PdI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt; I didn't demo the image generation because when I ran it locally, it didn't work out of the box. Turns out...if you want to use the Imagen model, you'll need to use Vertex AI Service, which requires a different auth method and running a backend (Node.js). Trust that the image generation worked when using Google AI Studio. :)&lt;/p&gt;

&lt;p&gt;Thanks for reading. If you're interested in seeing a deployed version, drop a comment.&lt;/p&gt;

</description>
      <category>deved</category>
      <category>learngoogleaistudio</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>AI Writing Assistant for Sci-fi Authors</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Mon, 11 Nov 2024 04:40:25 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/ai-writing-assistant-for-sci-fi-authors-1d5g</link>
      <guid>https://dev.to/rachel_cheuk/ai-writing-assistant-for-sci-fi-authors-1d5g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pgai"&gt;Open Source AI Challenge with pgai and Ollama &lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;An AI Writing Assistant for Science Fiction Authors to help overcome writer's block with plot ideas based on their current writing. To build this project, I used postgresql with the &lt;code&gt;pgai&lt;/code&gt; extension and Ollama. &lt;/p&gt;

&lt;p&gt;For this challenge, an author can enter text in the editor. The "AI assistant" will use this text to help the author brainstorm ideas for what can follow and generate new text. Additional attempted functionality includes saving and embedding story text to enable embedding queries.&lt;/p&gt;

&lt;p&gt;Frontend and backend are separate and spun up as docker containers. Frontend is built with NuxtJS/Vue/TailwindCSS/DaisyUI; Backend is built on FastAPI/Python/Postgresl leveraging the &lt;code&gt;pgai&lt;/code&gt; and &lt;code&gt;pgvector&lt;/code&gt; extensions with Ollama and the generative AI model.&lt;/p&gt;

&lt;p&gt;I attempted to work with &lt;code&gt;pgvector&lt;/code&gt;, &lt;code&gt;pgvectorscale&lt;/code&gt;, and &lt;code&gt;pgai vectorizer&lt;/code&gt;, but came upon several challenges in the process and ultimately didn't have enough time to overcome those challenges before the challenge deadline. &lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Quick Video Demo:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EwscamCTYy8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/meditatingdragon" rel="noopener noreferrer"&gt;
        meditatingdragon
      &lt;/a&gt; / &lt;a href="https://github.com/meditatingdragon/ollama-pgai-dev-challenge" rel="noopener noreferrer"&gt;
        ollama-pgai-dev-challenge
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Science Fiction Writing Assistant powered by AI
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;AI Writing Assistant for Sci-fi Authors&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;This project was developed for the &lt;a href="https://dev.to/challenges/pgai" rel="nofollow"&gt;Open Source AI Challenge with pgai and Ollama&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is a tool for science fiction authors to overcome writer's block by using AI as an assistant to help complete the story.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Tools&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Dev Challenge Tools&lt;/h4&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;pgai / postgresql&lt;/li&gt;
&lt;li&gt;ollama&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Other technologies&lt;/h4&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Nuxt.js&lt;/li&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;TailwindCSS&lt;/li&gt;
&lt;li&gt;DaisyUI&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Getting Started&lt;/h4&gt;

&lt;/div&gt;
&lt;p&gt;This project can be run with docker:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This should start several containers for the frontend, backend, ollama model, and postgresql database. There is also a container for the vectorizer worker, though it is not used in this project since I did not end up using OpenAI.&lt;/p&gt;
&lt;p&gt;For more details on the project, check out the &lt;a href="https://github.com/meditatingdragon/ollama-pgai-dev-challenge" rel="noopener noreferrer"&gt;challenge submission&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/meditatingdragon/ollama-pgai-dev-challenge" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Tools Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Database: Timescale Docker Image&lt;/li&gt;
&lt;li&gt;Frameworks: FastAPI (Python), NuxtJs (Javascript/Vue.js)&lt;/li&gt;
&lt;li&gt;Model: Ollama&lt;/li&gt;
&lt;li&gt;Hackathon Tools: &lt;code&gt;pgai&lt;/code&gt;, &lt;code&gt;ollama&lt;/code&gt;, &lt;code&gt;pgvector&lt;/code&gt;, &lt;code&gt;pgvectorscale&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This was one of my first AI projects where I dug further into the end-to-end implementation of AI applications. The idea of a writing assistant has been in my mind for many years though, and I'm glad I had a chance to tinker and explore this idea.&lt;/p&gt;

&lt;p&gt;As I haven't really worked with vector databases before, there was a bit of a learning curve understanding how to embed and store vector data. I know that if I had more time, I would work on implementing some search functionality that could leverage the power of embeddings and vector databases. &lt;/p&gt;

&lt;p&gt;I managed to successfully store text embeddings on saving a story, but did not have enough time to overcome some challenges I ran into while trying to query on these embeddings. I &lt;em&gt;think&lt;/em&gt; this would have required some configuration updates at different points in the application, but with the project set up the way it was, it was not a simple thing to do. This would be the next feature I would tackle if I had more time. &lt;/p&gt;

&lt;p&gt;I also wanted to experiment more with fine-tuning models for this particular use case supporting science fiction authors and being able to run a custom model with the &lt;code&gt;Modelfile&lt;/code&gt; in Ollama.&lt;/p&gt;

&lt;p&gt;The "product vision" would be to be able to fine tune which elements of a story can be used for "inspiration" for the author (different story elements, such as location, characters, events, etc.) and to include AI generated graphics to help with visualizing story assets. &lt;/p&gt;

&lt;p&gt;Overall, I learned a lot from this project and now have a better understanding of how &lt;code&gt;pgai&lt;/code&gt; and &lt;code&gt;Ollama&lt;/code&gt; can be used to build unique AI applications. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prize Categories:&lt;/strong&gt;&lt;br&gt;
Aspired for all 3 prize categories, but could only incorporate one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open-source Models from Ollama (Ollama via docker image with llama3.2 model)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>pgaichallenge</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>Nevertheless, Rachel Coded</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Tue, 09 Mar 2021 17:57:17 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/nevertheless-rachel-coded-3j81</link>
      <guid>https://dev.to/rachel_cheuk/nevertheless-rachel-coded-3j81</guid>
      <description>&lt;p&gt;2020 was an interesting year, where the pandemic disrupted work/life balance worldwide. I was planning on skipping this post, as I couldn't think of anything to write. Then I decided I'd start and see what came out.&lt;/p&gt;

&lt;h2&gt;
  
  
  My most recent achievement was…
&lt;/h2&gt;

&lt;p&gt;When I look back on 2020, I thought I would be searching for a new role for some stability. As the world began shutting down, that outlook changed entirely as I saw the challenges ahead for companies weathering the new, unprecedented 'storm'. Instead, I settled in for a longer journey developing different products and embracing a remote work culture. &lt;/p&gt;

&lt;p&gt;Through this new normal, I helped a handful of clients bring data to life, creating a real-time dashboard and overhauling a product to improve its data querying and presentation. This included re-architecting how data was organized to improve speed and query times. More recently, I've been helping manage remote hires and to cultivate a remote work culture.&lt;/p&gt;

&lt;p&gt;Outside of tech, I began rock climbing and just figured a challenging route that took over a month to figure out. 💪🏻 Haha, sometimes physical achievements feel greater than mental achievements...&lt;/p&gt;

&lt;h2&gt;
  
  
  My biggest goal is…
&lt;/h2&gt;

&lt;p&gt;...for my side hustle &lt;a href="https://www.eonzah.com" rel="noopener noreferrer"&gt;Eonzah&lt;/a&gt; - an interactive giveaway management platform - to set the foundation to secure its first paying customer. Welcoming beta users...reach out if interested! &lt;/p&gt;

&lt;h2&gt;
  
  
  My advice for allies to support underrepresented folks who code is...
&lt;/h2&gt;

&lt;p&gt;Take the time to &lt;strong&gt;listen&lt;/strong&gt; and &lt;strong&gt;practice empathy&lt;/strong&gt;. Be patient with things that aren't clear. Clarity comes with time as we learn to work better together. &lt;/p&gt;

&lt;p&gt;I guess, now that I've put all this on "paper", 2020 wasn't without good things. It just takes intentional thought to recall the good.&lt;/p&gt;

</description>
      <category>wecoded</category>
    </item>
    <item>
      <title>DO Hackathon: Blokii Image Maker - Submission</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 10 Jan 2021 19:38:18 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-submission-3jd0</link>
      <guid>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-submission-3jd0</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The Blokii Image Maker is a simple app that allows users to generate tech blog images. &lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Built for Business&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://img-maker.blokii.com" rel="noopener noreferrer"&gt;https://img-maker.blokii.com&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdxuuo5zo7heuv21dcl1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdxuuo5zo7heuv21dcl1a.png" alt="Alt App Screenshot" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjip89uj0cya5qz4jep8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjip89uj0cya5qz4jep8t.png" alt="Alt Text Settings" width="800" height="1095"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7a1unc4asboi7mpcu9dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7a1unc4asboi7mpcu9dt.png" alt="Alt Image Settings" width="794" height="1436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6jjbu8am65sq1qkl8dv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6jjbu8am65sq1qkl8dv9.png" alt="Alt Unsplash Dialog" width="800" height="976"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;The Blokii Image Maker allows users to generate an image with a title, subtitle, author byline, and tech icons. The images are downloadable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;Frontend:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/blokii" rel="noopener noreferrer"&gt;
        blokii
      &lt;/a&gt; / &lt;a href="https://github.com/blokii/blokii-img-maker-client" rel="noopener noreferrer"&gt;
        blokii-img-maker-client
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Blokii Image Maker&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;The Blokii Image Maker is built with the &lt;a href="https://quasar.dev" rel="nofollow noopener noreferrer"&gt;Quasar Framework&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install the dependencies&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Start the app in development mode (hot-code reloading, error reporting, etc.)&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;quasar dev&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Lint the files&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn run lint&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Build the app for production&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;quasar build&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Customize the configuration&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;See &lt;a href="https://quasar.dev/quasar-cli/quasar-conf-js" rel="nofollow noopener noreferrer"&gt;Configuring quasar.conf.js&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;In addition to leveraging the power of the Quasar Framework, it also uses the FontAwesome icon set and FeathersJS Client library to connect to the Feathers Backend server. It uses a Fontawesome Pro License, which is domain restricted and only works on certain domains.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contribute&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Read the &lt;a href="https://github.com/blokii/blokii-img-maker-clientCONTRIBUTE.MD" rel="noopener noreferrer"&gt;GUIDELINES&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/blokii/blokii-img-maker-client" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Backend:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/blokii" rel="noopener noreferrer"&gt;
        blokii
      &lt;/a&gt; / &lt;a href="https://github.com/blokii/blokii-img-gen-backend" rel="noopener noreferrer"&gt;
        blokii-img-gen-backend
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Blokii Image Maker&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This project uses &lt;a href="http://feathersjs.com" rel="nofollow noopener noreferrer"&gt;Feathers&lt;/a&gt;. An open source web framework for building modern real-time applications.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Project Notes&lt;/h3&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Unsplash API&lt;/h4&gt;

&lt;/div&gt;
&lt;p&gt;As per Unsplash's API Guidelines, this app requires an access key for using their API. This access key is retrieved as an environment variable that should be available at runtime. To learn more about creating an access key, see the &lt;a href="https://unsplash.com/developers" rel="nofollow noopener noreferrer"&gt;Unsplash Developers API Guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;CRUD&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;To demonstrate basic crud functionality, this project allows creating, listing, and deleting of images created to the server. The service framework used is &lt;a href="https://docs.feathersjs.com/api/services.html" rel="nofollow noopener noreferrer"&gt;FeathersJS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This project uses socket.io to communicate with the server. Though if using the rest implementation, the following available API endpoints would include the following:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;GET      /unsplash (get images from unsplash, proxies a request)
GET      /unsplash/track (sends a tracking request to unsplash per dev guidelines)
GET      /images (retrieves images)
GET      /images/:id (retrieves image by id)
POST     /images&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/blokii/blokii-img-gen-backend" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.apache.org/licenses/LICENSE-2.0" rel="noopener noreferrer"&gt;Apache 2.0&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I started blogging more on tech topics in the past couple of years. It was always a chore to find an image that would suit the blog post if it wasn't for an existing project with screenshots. I thought if it were possible to generate images automatically, it would save me some time as I came up with new blog posts. &lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;This application consisted of a frontend and a backend. The frontend was built with the Quasar Framework. The backend was built with FeatherJS. Both applications were deployed using Digital Ocean. &lt;/p&gt;

&lt;p&gt;The frontend would be built as a Singe Page Application and served as a static site via Digital Ocean. The Feathers Application would be deployed and hosted using Digital Ocean's App service. &lt;/p&gt;

&lt;p&gt;I really appreciated how easy it was to get up and running with Digital Ocean. At first, I wasn't sure if it'd be easier to serve the frontend via the backend, and that's how I originally deployed, but I found myself making more changes to the frontend than the backend and ultimately opted for separate deployment pipelines as I found it more a hassle to update the backend every time I wanted to push a frontend change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;p&gt;Technologies that I used for this project include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/products/app-platform/" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.feathersjs.com/guides/" rel="noopener noreferrer"&gt;FeathersJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://quasar.dev" rel="noopener noreferrer"&gt;Quasar Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unsplash.com/developers" rel="noopener noreferrer"&gt;Unsplash API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback always appreciated!&lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>digitalocean</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>DO Hackathon: Blokii Image Maker - Deployment</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 10 Jan 2021 18:53:49 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-deployment-4fo6</link>
      <guid>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-deployment-4fo6</guid>
      <description>&lt;p&gt;I think it's time to deploy. Let's check the feature checklist.&lt;/p&gt;

&lt;p&gt;The user interface can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add a title&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add a subtitle&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add a byline&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to select a font family for the text&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to change the fill and stroke color of the text&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add technologies&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Select image from Unsplash&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Download the final image from the website&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;Deploy site as a static SPA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;del&gt;Proxy unsplash searches and downloads from the frontend with the Unsplash API auth token&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;Deploy to Digital Ocean as an App Droplet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment is all that remains. &lt;/p&gt;

&lt;h3&gt;
  
  
  Digital Ocean
&lt;/h3&gt;

&lt;p&gt;When it comes to Platform-as-a-Service (PaaS) providers, I've used Heroku in the past, though never in production. For production applications, I've relied more on AWS and Google Cloud. &lt;/p&gt;

&lt;p&gt;With all these experiences to draw on, I've found PaaS platforms great for individual maintainers. It takes the hassle out of setting up, configuring, and securing Cloud Infrastructure. Nowadays, I'm certainly more willing to consider PaaS over Cloud providers due to cost and simplicity. Cloud infrastructure isn't a bad option if you have the engineering team to support it. Though... most clients and roles I've been in ... usually don't. I've also increasingly grown to dislike configuring servers for production use...I'd rather spend my time building the application.&lt;/p&gt;

&lt;p&gt;Using Digital Ocean was a pleasant experience overall. I knew I wanted to deploy the frontend and backend separately. The frontend would be compiled as a Single-Page Application (SPA) that pointed to the backend. The backend was a simple Node.js Application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploying the SPA
&lt;/h4&gt;

&lt;p&gt;Quasar provides a build command that generates the SPA (&lt;code&gt;quasar build&lt;/code&gt;). It was quite easy to deploy a static site using Digital Ocean by creating a new App.&lt;/p&gt;

&lt;p&gt;I connected my Github account, selected the repo, opted for auto deploy (the ease of CI/CD is such a treat nowadays...) and selected a location and repo branch to deploy. DO detected that my application was a web application. I selected to build the App as a static site. I then added the environment variable needed to point to the Feathers server and the necessary build commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ug7bhktrxihu0rzna1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ug7bhktrxihu0rzna1f.png" alt="Alt Text" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The build commands included installing the Quasar CLI, so it could be used to build the project, which would by default output the files in to the &lt;code&gt;dist/spa&lt;/code&gt; folder. I would then need to specify that the &lt;code&gt;dist/spa&lt;/code&gt; folder was the directory in which to serve the application from.&lt;/p&gt;

&lt;p&gt;Once all the values were set, it was ready to build and launch!&lt;/p&gt;

&lt;p&gt;I pushed the button...and it started building. And then it was live! I could see the application was successfully being hosted, but in order for it to work with Unsplash, I would also need the server running.&lt;/p&gt;

&lt;p&gt;I ran into some issues where I just didn't put in the right information (specifying the right build folder, installing the Quasar CLI), but that's on me. Overall, this was far simpler than some other services I've used for SPA deployment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploying the FeathersJS
&lt;/h4&gt;

&lt;p&gt;I wrote a quick write-up detailing how to deploy &lt;a href="https://dev.to/rachel_cheuk/deploying-realtime-feathersjs-to-digitalocean-app-platform-4df5"&gt;Feathers on Digital Ocean&lt;/a&gt; at the start of this Hackathon. In going through that process, I already knew what I needed to do to run a Feathers Server. &lt;/p&gt;

&lt;p&gt;The main difference with this deployment was the addition of environment variables, primarily the &lt;code&gt;UNSPLASH_ACCESS&lt;/code&gt; key, which would be needed to successfully query the Unsplash API. &lt;/p&gt;

&lt;p&gt;I followed the same steps: connecting the repo, selecting the location, and defining the application variables. Again, Digital Ocean detected that it was a Node.js application, so it offered the related Node.js settings. I updated the port, added in the &lt;code&gt;UNSPLASH_ACCESS&lt;/code&gt; key environment variable, and added in the build commands.&lt;/p&gt;

&lt;p&gt;Then I selected the plan for the server and launched the application. Once the build was complete, it was live.&lt;/p&gt;

&lt;h4&gt;
  
  
  Domain Management
&lt;/h4&gt;

&lt;p&gt;In order for the frontend to reach the backend, I had to set up the domain correctly. &lt;/p&gt;

&lt;p&gt;Digital Ocean offered to manage the domain for me or provided a simple copy/paste solution so I could update the domain records myself. I opted for the latter, since I already have something hosted on another provider that is managing that domain. &lt;/p&gt;

&lt;p&gt;After updating the domain, it was fairly easy to test whether things were connected appropriately. If the server returned results from Unsplash to display on the frontend, everything would be working as expected. &lt;/p&gt;

&lt;p&gt;I refreshed the page...and things were working!&lt;/p&gt;

&lt;p&gt;It was finally LIVE! &lt;/p&gt;

&lt;p&gt;Check it out: &lt;a href="https://img-maker.blokii.com" rel="noopener noreferrer"&gt;Blokii Image Maker - https://img-maker.blokii.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, to test it, I used it to make this blog post image! &lt;/p&gt;

&lt;p&gt;(and in doing so, have already seen areas that could be improved, haha).&lt;/p&gt;

&lt;h6&gt;
  
  
  Please note, it's not very mobile friendly yet...
&lt;/h6&gt;

&lt;p&gt;Let me know what you think. Feedback always welcome! &lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>digitalocean</category>
      <category>feathersjs</category>
      <category>quasar</category>
    </item>
    <item>
      <title>DO Hackathon: Blokii Image Maker - The Unsplash API &amp; Feathers Server</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 10 Jan 2021 17:50:12 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-the-unsplash-api-feathers-server-134c</link>
      <guid>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-the-unsplash-api-feathers-server-134c</guid>
      <description>&lt;p&gt;In order to open up image select options, I decided to use the &lt;a href="https://unsplash.com/developers" rel="noopener noreferrer"&gt;Unsplash API&lt;/a&gt;. As an &lt;a href="https://unsplash.com/@meditatingdragon" rel="noopener noreferrer"&gt;Unsplash contributor&lt;/a&gt; and user, I was confident this would open up creative possibilities for users and provide more unique images as new images become available.&lt;/p&gt;

&lt;p&gt;The API has requirements for &lt;a href="https://unsplash.com/documentation#guidelines--crediting" rel="noopener noreferrer"&gt;attribution&lt;/a&gt; and &lt;a href="https://unsplash.com/documentation#public-authentication" rel="noopener noreferrer"&gt;security&lt;/a&gt; that required I develop a server proxy for any Unsplash API requests. &lt;/p&gt;

&lt;h3&gt;
  
  
  The User Interface
&lt;/h3&gt;

&lt;p&gt;Due to the attribution requirements, I had to design the user interface with this in mind. I developed the &lt;strong&gt;UnsplashCard&lt;/strong&gt; that would be responsible for most of this, displaying a single image with the necessary metadata - this primarily included displaying the right image url and contributor information (name, location, etc.) with the image. &lt;/p&gt;

&lt;p&gt;I created a simple hover effect that would show all this at once:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcv51texvwlg69rb49ywk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcv51texvwlg69rb49ywk.png" alt="Alt Text" width="676" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The information would link to the user's profile and open a new page if clicked. Once I had the UnsplashCard component complete, I could list a bunch of results from the API.&lt;/p&gt;

&lt;p&gt;I used a popup dialog to display results, mostly to keep things simple (I could keep the dialog in the same page and not have to make too many new views/layouts).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8pumsc4w7z531w7cvhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8pumsc4w7z531w7cvhh.png" alt="Alt Text" width="800" height="976"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To keep track of the image selected by the user, I stored this information in the vuex state. Once the user selected the image, I would send a download request to the proxy server so it could relay that to Unsplash and properly track the images selected by users as required for API usage. &lt;/p&gt;

&lt;h3&gt;
  
  
  The FeathersJS Server
&lt;/h3&gt;

&lt;p&gt;To proxy the Unsplash API requests, I created a simple &lt;a href="https://docs.feathersjs.com/guides/" rel="noopener noreferrer"&gt;FeathersJS&lt;/a&gt; project. If I wanted to develop user accounts down the road, Feathers provides that out-of-the-box. &lt;/p&gt;

&lt;p&gt;I added the &lt;code&gt;unsplash-js&lt;/code&gt; library following the &lt;a href="https://github.com/unsplash/unsplash-js" rel="noopener noreferrer"&gt;Unsplash documentation&lt;/a&gt;, which I found quite straightforward. &lt;/p&gt;

&lt;p&gt;Then I created a custom service in Feathers called Unsplash. It would implement the &lt;a href="https://docs.feathersjs.com/api/services.html#find-params" rel="noopener noreferrer"&gt;find() service method&lt;/a&gt; and use the provided params to make an API request.&lt;/p&gt;

&lt;p&gt;To use the Unsplash API, I had to create a developer account and generate an access key for the application. I stored this as an environment variable for security purposes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Find Service Method
&lt;/h4&gt;

&lt;p&gt;The find service method would handle two kinds of request: a search request and a download request. &lt;/p&gt;

&lt;p&gt;To implement the search request, I defaulted some variables, such as how to order the search results. I think ideally, this is probably something the user should be able to select. Right now, it defaults to &lt;code&gt;latest&lt;/code&gt;, which I've found doesn't always yield the best results 😅. I mapped other variables to the pagination component options provided by Quasar so I could have pagination of results. &lt;/p&gt;

&lt;p&gt;Once the results return, I forward that back to the frontend to be displayed in the Unsplash Dialog.&lt;/p&gt;

&lt;p&gt;The download request is passed as an action parameter (&lt;code&gt;action: trackPhoto&lt;/code&gt;). If the request is to send a download request to Unsplash, it does so using the download_location param saved when the user selected the image (as detailed &lt;a href="https://github.com/unsplash/unsplash-js#photostrackdownloadarguments-additionalfetchoptions" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Once this returns, the response is forwarded to the frontend to confirm the request is successful.&lt;/p&gt;

&lt;p&gt;That's it for the server! &lt;/p&gt;

&lt;p&gt;Onto deployment.&lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>webdev</category>
      <category>feathersjs</category>
      <category>unsplash</category>
    </item>
    <item>
      <title>DO Hackathon: Blokii Image Maker - The Quasar Frontend</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 10 Jan 2021 17:00:17 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-the-quasar-frontend-198k</link>
      <guid>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-the-quasar-frontend-198k</guid>
      <description>&lt;h3&gt;
  
  
  Background and Assumptions
&lt;/h3&gt;

&lt;p&gt;For this series, I'll be focused more on explaining thought process than going over code. If anyone has questions about code, feel free to leave a comment. In general, I'm assuming a basic understanding of the involved technologies, so I won't spend too much time breaking down code snippets. Both repositories are available for review and will be linked at the end of this series.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Layout
&lt;/h3&gt;

&lt;p&gt;As this app is focused on generating an image, I wanted the focus of the app to be the image. I also knew there needed to be options for changing the image design.&lt;/p&gt;

&lt;p&gt;This meant there would need to be two primary components that would make up the layout: the image and the settings. The image would need to take up most of the available real estate. The settings would take up the remaining.&lt;/p&gt;

&lt;p&gt;I decided to add in a header for navigation and a footer for application links and info. The navigation would only lead to two other pages: about and help, which I would populate with some content to explain the app vision and FAQs. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Image
&lt;/h3&gt;

&lt;p&gt;After some research, I determined the features I wanted to implement could be accomplished with HTML Canvas, so the image design would take place using a canvas element.&lt;/p&gt;

&lt;p&gt;I created a component called the Blokii Canvas to encapsulate the canvas and contain all related canvas drawing logic. It would watch for changes in settings selected by the user and draw and redraw the canvas based on user settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Settings
&lt;/h3&gt;

&lt;p&gt;Following the features defined, the user would need to be able to add a title, subtitle, and author byline. None are required but should be optionally customizable. The settings component would need to allow the user to customize these four elements. The user should also be able to change the color of the text and change the font family if desired. &lt;/p&gt;

&lt;p&gt;To keep things simple, I pre-selected a subset of font faces that I would allow the user to select, taking a few from each family of fonts. &lt;/p&gt;

&lt;p&gt;I also needed to enable the user to select an image or provide an image url to use as the background. If they wanted to download the image, a filename would also need to be provided. Finally, the user should also be able to add technology icons to their image. I pre-selected these options as well to keep things simple. &lt;/p&gt;

&lt;p&gt;Once I grouped the functionality into their respective parts, I could break things down further.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;I used vuex for state management so I could track the user's settings globally. This allowed me to update the canvas as soon as the user changed something. &lt;/p&gt;

&lt;h4&gt;
  
  
  Image Filters, Text Options
&lt;/h4&gt;

&lt;p&gt;I stored image filter selections and text options in their respective state modules to keep them separate. Each css filter had a value associated with it to adjust the strength of the filter. &lt;/p&gt;

&lt;h4&gt;
  
  
  Global State
&lt;/h4&gt;

&lt;p&gt;The global state module kept track of application level settings, like the filename of the downloadable image.&lt;/p&gt;

&lt;h3&gt;
  
  
  BlokiiCanvas
&lt;/h3&gt;

&lt;p&gt;Developing the BlokiiCanvas was a bit of trial and error - as tends to be the case with design, sometimes it's hard to know if it looks good until you see it. This includes adjusting the default font size and positions. As these are fixed, I had to be sure the final result would be OK. I suppose it'd also be great if these settings were also customizable. Perhaps that can go into the next release.&lt;/p&gt;

&lt;p&gt;Once I figured out how to add the image to the canvas and apply the css filters, I could easily iterate on it and create a bunch of if/else cases based on the outcome of user settings. &lt;/p&gt;

&lt;p&gt;This gave me a drawable canvas that could produce this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fftnlhf1708ifwk5p76md.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fftnlhf1708ifwk5p76md.png" alt="Alt Text" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good enough for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings Panel
&lt;/h3&gt;

&lt;p&gt;Once I figured out how to lay things out on the canvas, I could make it variable. The settings panel would allow users to select options. I provided options for selecting font faces, title, subtitle, and byline text, and adjusting font colors. Finally, I also needed to allow the user to select the technologies they are blogging about, so I created a dropdown with options they could search against. I stored these settings as a json object in the vuex state. &lt;/p&gt;

&lt;p&gt;Here's the text settings panel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4r6vm6wnid9yosmnqv8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4r6vm6wnid9yosmnqv8e.png" alt="Alt Text" width="800" height="1095"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's quite simple in how it accepts user input. I probably should add more validation...but there's limited validation at this point in time. &lt;/p&gt;

&lt;p&gt;And here's the image settings panel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzsiorhxopanlq6bq8abr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzsiorhxopanlq6bq8abr.png" alt="Alt Text" width="794" height="1436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image settings allows user to either enter an existing url or to pick an image from Unsplash. Users can then opt to select an image filter for the selected image. &lt;/p&gt;

&lt;h3&gt;
  
  
  Download
&lt;/h3&gt;

&lt;p&gt;To download the image, the user must enter a filename to name the downloaded file. The browser then convert the data from the canvas and encodes it to an image file for the user to download. &lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Check
&lt;/h3&gt;

&lt;p&gt;Let's see where we're at now...&lt;/p&gt;

&lt;p&gt;The user interface can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add a title&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add a subtitle&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add a byline&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to select a font family for the text&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to change the fill and stroke color of the text&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Enable the user to add technologies&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;Select image from Unsplash&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Download the final image from the website&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;Deploy site as a static SPA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll cover Unsplash in the next section as I build out the server API. &lt;/p&gt;

&lt;p&gt;Questions, comments, thoughts?&lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>webdev</category>
      <category>quasar</category>
      <category>showdev</category>
    </item>
    <item>
      <title>DO Hackathon: Blokii Image Maker - Defining the Project</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 10 Jan 2021 04:57:40 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-defining-the-project-5fmo</link>
      <guid>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-defining-the-project-5fmo</guid>
      <description>&lt;h3&gt;
  
  
  The App
&lt;/h3&gt;

&lt;p&gt;The goal of the Blokii Image Maker is to allow users to easily generate an image that enables them to download and create a blog post with common tech frameworks they're writing about. &lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Features
&lt;/h3&gt;

&lt;p&gt;As I planned this project, I wanted to define what &lt;em&gt;done&lt;/em&gt; would mean for this hackathon, otherwise I tend to use my time inefficiently. For the purpose of this hackathon, I decided &lt;em&gt;done&lt;/em&gt; would mean the following:&lt;/p&gt;

&lt;p&gt;The user interface can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable the user to add a title&lt;/li&gt;
&lt;li&gt;Enable the user to add a subtitle&lt;/li&gt;
&lt;li&gt;Enable the user to add a byline&lt;/li&gt;
&lt;li&gt;Enable the user to select a font family for the text&lt;/li&gt;
&lt;li&gt;Enable the user to change the fill and stroke color of the text&lt;/li&gt;
&lt;li&gt;Enable the user to add technologies&lt;/li&gt;
&lt;li&gt;Select image from Unsplash&lt;/li&gt;
&lt;li&gt;Download the final image from the website&lt;/li&gt;
&lt;li&gt;Deploy site as a static SPA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxy unsplash searches and downloads from the frontend with the Unsplash API auth token&lt;/li&gt;
&lt;li&gt;Deploy to Digital Ocean as an App Droplet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the goals were defined, I could start checking off the tasks one-by-one.&lt;/p&gt;

&lt;p&gt;Let's see how things go...&lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>webdev</category>
      <category>quasar</category>
      <category>feathers</category>
    </item>
    <item>
      <title>DO Hackathon: Blokii Image Maker - An Introduction</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Sun, 10 Jan 2021 04:27:16 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-an-introduction-2dfj</link>
      <guid>https://dev.to/rachel_cheuk/do-hackathon-blokii-image-maker-an-introduction-2dfj</guid>
      <description>&lt;h3&gt;
  
  
  The Project - Blokii Image Maker
&lt;/h3&gt;

&lt;p&gt;The Blokii Image Maker is a simple image generator for tech blogs. It overlays common framework icons on an image provided by the user and allows basic filtering and titles to be applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;p&gt;The tech stack chosen for this project is Quasar and FeathersJS. The (simple) application could have been built on just Quasar alone if it wasn't for the Unsplash API that requires a backend proxy to secure API auth tokens. &lt;/p&gt;

&lt;p&gt;As I've been working with FeatherJS quite a bit, I decided to use &lt;a href="https://docs.feathersjs.com/guides/" rel="noopener noreferrer"&gt;FeathersJS&lt;/a&gt; for the backend to create a simple api that could proxy my Unsplash API requests.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://quasar.dev" rel="noopener noreferrer"&gt;Quasar Framework&lt;/a&gt; is used as the frontend framework. In this series of posts, I'll write about how the project was developed.&lt;/p&gt;

&lt;p&gt;Let's get hacking.&lt;/p&gt;

</description>
      <category>dohackathon</category>
      <category>webdev</category>
      <category>quasar</category>
      <category>feathers</category>
    </item>
    <item>
      <title>Deploying Realtime FeathersJS to DigitalOcean App Platform</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Thu, 17 Dec 2020 19:20:18 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/deploying-realtime-feathersjs-to-digitalocean-app-platform-4df5</link>
      <guid>https://dev.to/rachel_cheuk/deploying-realtime-feathersjs-to-digitalocean-app-platform-4df5</guid>
      <description>&lt;p&gt;Recently, the &lt;a href="https://dev.to/devteam/announcing-the-digitalocean-app-platform-hackathon-on-dev-2i1k"&gt;DigitalOcean App Hackathon&lt;/a&gt; was announced. If you've been following &lt;a href="https://dev.to/rachel_cheuk/getting-started-with-realtime-events-and-streaming-data-in-js-4j6l"&gt;my articles on FeathersJS&lt;/a&gt;, this is a great opportunity to try out Feathers. &lt;/p&gt;

&lt;p&gt;In this brief article, I'll show how to launch your own Feathers instance on Digital Ocean.&lt;/p&gt;

&lt;p&gt;Similar to Heroku and other PaaS (Platform-as-a-Service) providers, with the DigitalOcean App service, it's quite easy and straightforward to deploy an application. Digital Ocean provides a full workflow to add in environment variables, add a managed database, build on code push, and to deploy your application. If you don't want to deal with the stress of CI/CD (continuous integration/continuous delivery) and DevOps for an application, this may be a good option. &lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Application
&lt;/h3&gt;

&lt;p&gt;To get started, I generated a basic Feathers application using the following settings:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;feathers generate app&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;? Do you want to use JavaScript or TypeScript? ...JavaScript
? Project name ...do-feathers
? Description ...Digital Ocean Feathers App
? What folder should the &lt;span class="nb"&gt;source &lt;/span&gt;files live &lt;span class="k"&gt;in&lt;/span&gt;? ...src
? Which package manager are you using &lt;span class="o"&gt;(&lt;/span&gt;has to be installed globally&lt;span class="o"&gt;)&lt;/span&gt;? ...Yarn
? What &lt;span class="nb"&gt;type &lt;/span&gt;of API are you making? ...Realtime via Socket.io
? Which testing framework &lt;span class="k"&gt;do &lt;/span&gt;you prefer? ...Jest
? This app uses authentication ...Yes
? Which coding style &lt;span class="k"&gt;do &lt;/span&gt;you want to use? ...ESLint
? What authentication strategies &lt;span class="k"&gt;do &lt;/span&gt;you want to use? &lt;span class="o"&gt;(&lt;/span&gt;See API docs &lt;span class="k"&gt;for &lt;/span&gt;all 180+ supported oAuth providers&lt;span class="o"&gt;)&lt;/span&gt; ...Username + Password &lt;span class="o"&gt;(&lt;/span&gt;Local&lt;span class="o"&gt;)&lt;/span&gt;
? What is the name of the user &lt;span class="o"&gt;(&lt;/span&gt;entity&lt;span class="o"&gt;)&lt;/span&gt; service? ...users
? What kind of service is it? ...NeDB
? What is the database connection string? &lt;span class="o"&gt;(&lt;/span&gt;nedb://../data&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This scaffolded a barebones application.&lt;/p&gt;

&lt;p&gt;I updated the &lt;code&gt;index.html&lt;/code&gt; in &lt;code&gt;/public/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I pushed this to &lt;a href="https://github.com/meditatingdragon/do-feathers" rel="noopener noreferrer"&gt;my github repo&lt;/a&gt;. &lt;/p&gt;
&lt;h3&gt;
  
  
  Digital Ocean App Platform
&lt;/h3&gt;

&lt;p&gt;To create a new App, on the home screen, click the Create App button. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fahz978dznto4j852i0ou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fahz978dznto4j852i0ou.png" alt="Alt Click Create App Button" width="698" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will walk you through creating an app.&lt;/p&gt;
&lt;h4&gt;
  
  
  Connect Github to Digital Ocean
&lt;/h4&gt;

&lt;p&gt;The first step will be to connect your Github to Digital Ocean. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh0qpz5yps68w9g7ped8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh0qpz5yps68w9g7ped8a.png" alt="Alt Connect Github to Digital Ocean" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the prompts to authorize your account, and either allow access to all repositories or only select repositories. &lt;/p&gt;

&lt;p&gt;Select the repository with your feathers app.&lt;/p&gt;
&lt;h4&gt;
  
  
  Name Your App and Select Deploy Branch
&lt;/h4&gt;

&lt;p&gt;You'll be asked to name your application, select a region, and select a branch to deploy your application from. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3k6ur9n4ctegyqkg01t3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3k6ur9n4ctegyqkg01t3.png" alt="Alt Name Application and select deploy branch" width="800" height="939"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also decide if you want Digital Ocean to automatically rebuild your application and deploy it for you once you push a new commit.&lt;/p&gt;
&lt;h4&gt;
  
  
  Configure Application
&lt;/h4&gt;

&lt;p&gt;Digital Ocean will attempt to detect the type of application you are trying to run. It should detect that you have a Node.js based application and offer some default options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmcurigf9dsie59o2a4de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmcurigf9dsie59o2a4de.png" alt="Alt Configure Application Screen" width="800" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Update the settings as needed.&lt;/p&gt;

&lt;p&gt;Depending on your application, you may need to update the Environment Variables, Build Commands, Run Command, and Port.&lt;/p&gt;

&lt;p&gt;Environment variables should be used to store 3rd-party API credentials and database settings. &lt;/p&gt;

&lt;p&gt;For the default Feathers application, at a minimum, you'll need to update the port to 3030, which is the default setting for a Feathers Application (though you can change it if you choose). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frk2etop9th7z1vyl6xnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frk2etop9th7z1vyl6xnx.png" alt="Alt Update port to 3030" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Finalize and Launch
&lt;/h4&gt;

&lt;p&gt;Finally, you'll be asked to select a plan. Choose one that meets your needs. If you're participating in the hackathon, you'll be given $50 credit for 60 days to use.&lt;/p&gt;

&lt;p&gt;Once you're ready, hit the magic button: Launch Basic App.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sample Deployment
&lt;/h3&gt;

&lt;p&gt;Take a look at my deployment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftkopx4dtxjacnjbqjf33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftkopx4dtxjacnjbqjf33.png" alt="Alt Digital Ocean Deployment" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's available here: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://do-feathers-tc4py.ondigitalocean.app/" rel="noopener noreferrer"&gt;https://do-feathers-tc4py.ondigitalocean.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the repo used:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/meditatingdragon" rel="noopener noreferrer"&gt;
        meditatingdragon
      &lt;/a&gt; / &lt;a href="https://github.com/meditatingdragon/do-feathers" rel="noopener noreferrer"&gt;
        do-feathers
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;do-feathers&lt;/h1&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;This project uses &lt;a href="http://feathersjs.com" rel="nofollow noopener noreferrer"&gt;Feathers&lt;/a&gt;. An open source web framework for building modern real-time applications.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Getting up and running is as easy as 1, 2, 3.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make sure you have &lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;NodeJS&lt;/a&gt; and &lt;a href="https://www.npmjs.com/" rel="nofollow noopener noreferrer"&gt;npm&lt;/a&gt; installed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install your dependencies&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;cd path/to/do-feathers
npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start your app&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Testing&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Simply run &lt;code&gt;npm test&lt;/code&gt; and all your tests in the &lt;code&gt;test/&lt;/code&gt; directory will be run.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Scaffolding&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Feathers has a powerful command line interface. Here are a few things it can do:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;$ npm install -g @feathersjs/cli          # Install Feathers CLI

$ feathers generate service               # Generate a new Service
$ feathers generate hook                  # Generate a new Hook
$ feathers help                           # Show all commands
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Help&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;For more information on all the things you can do with Feathers visit &lt;a href="http://docs.feathersjs.com" rel="nofollow noopener noreferrer"&gt;docs.feathersjs.com&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/meditatingdragon/do-feathers" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;I don't plan to keep it up long though, as I hope to deploy another app for the hackathon, so don't be surprised if it's no longer available by the time you read this!&lt;/p&gt;

&lt;h4&gt;
  
  
  Your Turn
&lt;/h4&gt;

&lt;p&gt;Let me know if you run into any issues. Happy Hacking! &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>digitalocean</category>
      <category>feathers</category>
    </item>
    <item>
      <title>Getting Started with Realtime Events and Streaming Data (in JS)</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Tue, 01 Dec 2020 18:08:23 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/getting-started-with-realtime-events-and-streaming-data-in-js-4j6l</link>
      <guid>https://dev.to/rachel_cheuk/getting-started-with-realtime-events-and-streaming-data-in-js-4j6l</guid>
      <description>&lt;h2&gt;
  
  
  About this Series
&lt;/h2&gt;

&lt;p&gt;In this series, I'll be exploring how to develop an event-driven web applications in Javascript. We'll use Vue on the frontend and FeathersJS on the backend to accomplish this. If you're interested in learning more about developing real-time and streaming data applications, follow this series. This will include developing chat-based apps, streaming videos or audio apps,  device-driven apps, such as those in the Internet-of-Things space, and much more. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This series assumes familiarity with Javascript. It also assumes familiarity with basic Vue and Node.js concepts&lt;/strong&gt;, such as setting up a simple project in Vue/Node. I'll do my best to break down more complex concepts. &lt;em&gt;If anything is unclear, please leave a comment so the article can be updated for clarity.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Realtime and Streaming Data?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Real-time_data" rel="noopener noreferrer"&gt;Real-time data&lt;/a&gt; is data that is delivered immediately after collection. With ongoing improvements in hardware and computing power, it has become increasingly common for businesses to provide real-time analysis of data to identify potential issues or opportunities. Data collected can be transformed "on-the-fly" and presented to decision-makers instantly.&lt;/p&gt;

&lt;p&gt;Traditionally, this was seen with devices and Geographical Information Systems (GIS) which would frequently emit sensor and/or location data. &lt;/p&gt;

&lt;p&gt;With the increasing use of AI and data warehousing techniques, it's fairly common now to see real-time processed data. For high-volume applications, it's increasingly important to be able to update sites in real-time as the state of the system changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time Events and Data Scenarios
&lt;/h2&gt;

&lt;p&gt;Applications of real-time data will only continue to increase with time. Here are a few common ones to think about:&lt;/p&gt;

&lt;h3&gt;
  
  
  Health and Fitness Devices
&lt;/h3&gt;

&lt;p&gt;As technology continues to improve, the advent of devices providing instant feedback will continue to increase in order to optimize the care and service doctors can provide. Medical equipment full of sensors will often need to transmit the information instantly in order to provide the doctor and patient the information needed to make informed decisions. In the past, X-rays would take days to process and develop. Now it's available within minutes. Other similar diagnostic procedures are increasingly becoming available to provide near real-time feedback for doctors to make decisions on a patient's condition.&lt;/p&gt;

&lt;p&gt;Similarly, fitness trackers can transmit data such as heart rate and record your activity as you exercise or sleep. It can alert you when you've hit your daily step goals or warn you that you're working too hard. These devices all rely on real-time updates to keep the user informed as an event occurs.&lt;/p&gt;

&lt;h3&gt;
  
  
  E-Commerce &amp;amp; Scheduling
&lt;/h3&gt;

&lt;p&gt;Managing inventory is important for customer satisfaction. Inventory is also finite, so when a user makes a purchase, the item is typically deducted from inventory. This generally works fine on low volume sites where a single user may only make one purchase for a single item at any given time. But what happens when multiple users try to purchase the same item at the same time?&lt;/p&gt;

&lt;p&gt;Only one person will be able to complete the purchase. The other orders will need to be canceled once it's been discovered that the product is no longer available. This can lead to a terrible customer experience if the time taken to handle this exceeds a customer's patience and expectations. &lt;/p&gt;

&lt;p&gt;Through real-time event updates, customers can be informed the product has been purchased and the item can be removed from their shopping cart before the purchase can be completed. This would help better manage customer expectations. The same can be applied to booking or scheduling applications.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Operational Awareness
&lt;/h3&gt;

&lt;p&gt;Sometimes, monitoring data in real-time is important for business operations. This is generally true for any kind of heuristics or diagnostic platform for computing systems. For example, in preventing and mitigating cyberattacks, it's often necessary to track the flow of traffic entering a network. &lt;/p&gt;

&lt;p&gt;The sooner an attack is discovered, the more likely a business can recover from the attack or defend against an attack. In such cases, real-time updates are important to accurately display the current situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Realtime Data
&lt;/h2&gt;

&lt;p&gt;The most common way to receive real-time updates on a website is through a real-time transport such as a socket. Sockets maintain an open channel with the server, allowing data and event notifications to pass through.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://socket.io" rel="noopener noreferrer"&gt;Socket.io&lt;/a&gt; is a popular library to support such connections. &lt;a href="https://feathersjs.com/" rel="noopener noreferrer"&gt;FeathersJS&lt;/a&gt; supports this out-of-the-box and provides additional scaffolding features for building a robust backend to support real-time applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started with FeathersJS
&lt;/h3&gt;

&lt;p&gt;Getting started with Feathers is easy. I'll briefly go over how to create your own project so you can start using it. Afterward, I'll be using a pre-built project template to demonstrate different use cases. You can either create your own project or follow along using the same template. &lt;/p&gt;

&lt;h3&gt;
  
  
  Feathers Command Line Interface (CLI)
&lt;/h3&gt;

&lt;p&gt;Feathers provides a CLI to enable you to quickly generate a new application. Globally install the Feathers CLI to generate an app:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @feathersjs/cli -g&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a folder for your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir feathers-realtime
cd feathers-realtime/
feathers generate app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Feathers CLI will prompt you with questions to help you configure your project, including auth, test frameworks, and data source providers. Adjust these based on your preferences. &lt;strong&gt;Make sure to select socket.io for your project when asked about APIs.&lt;/strong&gt; Once complete, the CLI will automatically generate the project directory structure and files.&lt;/p&gt;

&lt;p&gt;To learn more about the generated files, visit &lt;a href="https://docs.feathersjs.com/guides/basics/generator.html#generating-the-application" rel="noopener noreferrer"&gt;the docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Project Template
&lt;/h3&gt;

&lt;p&gt;To start with a bit more functionality, I'm going to work from existing project templates within the FeathersJS community and build off these examples.&lt;/p&gt;

&lt;p&gt;For the frontend, we'll use the feathers-vuex-chat frontend as a starting point which leverages the &lt;a href="https://vuex.feathersjs.com/" rel="noopener noreferrer"&gt;feathers-vuex&lt;/a&gt; library:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/feathersjs-ecosystem" rel="noopener noreferrer"&gt;
        feathersjs-ecosystem
      &lt;/a&gt; / &lt;a href="https://github.com/feathersjs-ecosystem/feathers-chat-vuex" rel="noopener noreferrer"&gt;
        feathers-chat-vuex
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Feathers Chat built with Feathers-Vuex 3.0
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;feathers-chat-vuex&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://greenkeeper.io/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7387c0c07d635cffe3da754b49063127df91900537aa97ff483d6a29969eea25/68747470733a2f2f6261646765732e677265656e6b65657065722e696f2f66656174686572736a732d65636f73797374656d2f66656174686572732d636861742d767565782e737667" alt="Greenkeeper badge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Built on Feathers-Vuex 3.x&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the new version of the Feathers Chat single page application using &lt;a href="https://github.com/feathersjs-ecosystem/feathers-vuex" rel="noopener noreferrer"&gt;feathers-vuex&lt;/a&gt;.  There is another version available that is no longer maintained at &lt;a href="https://github.com/feathersjs-ecosystem/feathers-chat-vuex-0.7" rel="noopener noreferrer"&gt;https://github.com/feathersjs-ecosystem/feathers-chat-vuex-0.7&lt;/a&gt;.  It serves as a valuable comparison of the old API with the new API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.feathersjs.com/guides/basics/frontend.html" rel="nofollow noopener noreferrer"&gt;&lt;br&gt;
  &lt;img src="https://camo.githubusercontent.com/1743b8f5ba2173b486cb77987e7e4bb84a47dbb8e879504e1943b710d81bf569/68747470733a2f2f646f63732e66656174686572736a732e636f6d2f6173736574732f696d672f66656174686572732d636861742e39313936303738352e706e67" alt="Feathers Chat UI"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;API Setup&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;This project is designed to work alongside the &lt;a href="https://github.com/feathersjs/feathers-chat" rel="noopener noreferrer"&gt;&lt;code&gt;feathers-chat&lt;/code&gt;&lt;/a&gt; application.  Please make sure you have the &lt;code&gt;feathers-chat&lt;/code&gt; server app running before you try to use this one.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Project setup&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;yarn install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Compiles and hot-reloads for development&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;yarn serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Compiles and minifies for production&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;yarn build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Lints and fixes files&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;yarn lint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Customize configuration&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;See &lt;a href="https://cli.vuejs.org/config/" rel="nofollow noopener noreferrer"&gt;Configuration Reference&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/feathersjs-ecosystem/feathers-chat-vuex" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;For the backend, we'll use the feathers-chat backend as a starting point: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/feathersjs" rel="noopener noreferrer"&gt;
        feathersjs
      &lt;/a&gt; / &lt;a href="https://github.com/feathersjs/feathers-chat" rel="noopener noreferrer"&gt;
        feathers-chat
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Feathers real-time chat application
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Feathers Chat&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;
  &lt;a href="https://feathersjs.com" rel="nofollow noopener noreferrer"&gt;
    &lt;img width="180" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ffeathersjs%2Ffeathers%2F2b89e0b7fceb42f92c9139f16f3291fa3ff560f1%2Fdocs%2Fpublic%2Ffeathersjs.svg" alt="Feathers logo"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;A FeathersJS Chat Application&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://github.com/feathersjs/feathers-chat/actions?query=workflow%3ACI" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/feathersjs/feathers-chat/workflows/CI/badge.svg" alt="NPM version"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://replit.com/new/github/feathersjs/feathers-chat" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/466cd243dff7c8f32dae2f44bba68c081f6690728c2d0de1f3e5193bc6caeb77/68747470733a2f2f7265706c69742e636f6d2f62616467653f63617074696f6e3d54727925323046656174686572732532306f6e2532305265706c6974" alt="Replit"&gt;&lt;/a&gt; 
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;This repository includes the server application from the &lt;a href="https://dove.feathersjs.com/guides/basics/generator.html" rel="nofollow noopener noreferrer"&gt;official Feathers chat guide&lt;/a&gt; as well as chat frontend examples for different frameworks.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;API server&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;TypeScript&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;The TypeScript version of the chat API server can be found in the &lt;a href="https://github.com/feathersjs/feathers-chat./feathers-chat-ts/" rel="noopener noreferrer"&gt;feathers-chat-ts&lt;/a&gt;. To start it install the dependencies like this:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;cd feathers-chat-ts
npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then compile the source code and run the database migration which will initialize an SQLite database in the &lt;code&gt;feathers-chat.sqlite&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm run compile
npm run migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It can now be started with:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or in development mode with&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now go to &lt;a href="http://localhost:3030" rel="nofollow noopener noreferrer"&gt;http://localhost:3030&lt;/a&gt; to start chatting 🕊️&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Frontend&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Plain JavaScript&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;A plain JavaScript frontend can be found in the &lt;a href="https://github.com/feathersjs/feathers-chat./public/" rel="noopener noreferrer"&gt;public&lt;/a&gt; folder which is hosted statically by the &lt;a href="https://github.com/feathersjs/feathers-chat#api-server" rel="noopener noreferrer"&gt;api server examples&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;React&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;TBD&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;VueJS&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;TBD&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/feathersjs/feathers-chat" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The combined repo for this post can be found here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/meditatingdragon" rel="noopener noreferrer"&gt;
        meditatingdragon
      &lt;/a&gt; / &lt;a href="https://github.com/meditatingdragon/realtime-feathers" rel="noopener noreferrer"&gt;
        realtime-feathers
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Getting started with Realtime and Streaming Data in Javascript
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Feathers Realtime&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This repository parallels a blog post on developing event-driven applications using FeathersJS.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/meditatingdragon/realtime-feathers" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Real-time Transports
&lt;/h2&gt;

&lt;p&gt;As mentioned above, Feathers supports Socket.io as a real-time transport. It also supports &lt;a href="https://github.com/primus/primus" rel="noopener noreferrer"&gt;Primus&lt;/a&gt;, which is a wrapper for real-time frameworks, making it possible to adapt Feathers to existing real-time frameworks used by other parts of the business. &lt;/p&gt;

&lt;h2&gt;
  
  
  Hello World - Pushing Messages to the Frontend
&lt;/h2&gt;

&lt;p&gt;To kick off this project, I'm going to mock up some data on the backend to demonstrate real-time updates on the front end. We'll create a simple dashboard with different charts to display real-time data. We'll dive into more use-cases in the next series of posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the Project
&lt;/h3&gt;

&lt;p&gt;This template uses vue on the frontend. To run the development server, use &lt;code&gt;yarn serve&lt;/code&gt; within the &lt;code&gt;feathers-chat-vuex&lt;/code&gt; directory. This will start on port 8080 by default. Navigate to your browser, &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt; to view the web app.&lt;/p&gt;

&lt;p&gt;This project uses FeatherJS on the backend. To run the development server, use &lt;code&gt;npm run dev&lt;/code&gt;. This will start on &lt;a href="http://localhost:3030" rel="noopener noreferrer"&gt;http://localhost:3030&lt;/a&gt; by default. &lt;/p&gt;

&lt;p&gt;The frontend should already be configured to connect to the backend on port 3030 through the &lt;code&gt;/src/feathers-client.js&lt;/code&gt; setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocking the Data
&lt;/h3&gt;

&lt;p&gt;To keep this first post simple, I'll mock up data to be sent by the Feathers backend at regular intervals. We'll use event listeners to detect when a user connects to the server and begin the data push once a user connects. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;channels.js&lt;/code&gt;, every time a connection is established with the server, it begins sending data every 5 seconds. This data is randomly generated using a helper function, &lt;code&gt;getRandomInt&lt;/code&gt;. It provides data variables that I'll use to update the charts. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a more realistic use-case&lt;/strong&gt;, this data could be provided by a service or other data source (see below for a custom service implementation, which is a better approach). Consider sending logs which may provide a constant stream of log data. Or maybe you want to send binary data to display to the user, like an audio clip or video generated by another user.&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;let&lt;/span&gt; &lt;span class="nx"&gt;logins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&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;interval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connection&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;connection&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;// On a new real-time connection, add it to the anonymous channel&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// create 5 second interval to emit "dataAvailable" event with data payload&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sending new data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// remove one value, add a new one&lt;/span&gt;
      &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// send the data through the 'dataAvailable' event&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;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dataAvailable&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;messageCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;activeChatRooms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;recentLogins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;openTickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;closedTickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;unassignedTickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disconnect&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;connection&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="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&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;When you run &lt;code&gt;npm run dev&lt;/code&gt; to start the server, the server should start transmitting data once a user connects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sockets Handling in the Frontend
&lt;/h3&gt;

&lt;p&gt;Feathers provides a wrapper for the socket.io client that works seamlessly with a Feathers backend. Feathers-vuex integrates this library and also provides real-time socket event support within the vuex store. To get started, add the following libraries if not already in your project:&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;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;feathersjs&lt;/span&gt;&lt;span class="sr"&gt;/feathers @feathersjs/&lt;/span&gt;&lt;span class="nx"&gt;socketio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;feathersjs&lt;/span&gt;&lt;span class="sr"&gt;/authentication-client socket.io-client @vue/&lt;/span&gt;&lt;span class="nx"&gt;composition&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="nx"&gt;feathers&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;vuex&lt;/span&gt; &lt;span class="nx"&gt;feathers&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These packages have already been added to the project template. &lt;code&gt;@feathersjs/feathers&lt;/code&gt;, &lt;code&gt;@feathersjs/socketio-client&lt;/code&gt;, &lt;code&gt;@feathersjs/authentication-client&lt;/code&gt;, and &lt;code&gt;socket.io-client&lt;/code&gt; provide the connection framework to communicate with your Feathers server through the socket.io real-time transport. The remaining libraries provide support for Vue/Vuex on the frontend. &lt;/p&gt;

&lt;p&gt;By default, the &lt;code&gt;feathers-vuex&lt;/code&gt; library &lt;a href="https://vuex.feathersjs.com/service-plugin.html#realtime-by-default" rel="noopener noreferrer"&gt;defaults to real-time connections&lt;/a&gt; (the alternative being REST API calls, which you can also configure).&lt;/p&gt;

&lt;p&gt;If this is your first time using &lt;a href="https://vuex.feathersjs.com/" rel="noopener noreferrer"&gt;Feathers-Vuex&lt;/a&gt;, I would recommend reviewing the &lt;a href="https://vuex.feathersjs.com/api-overview.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, which documents the &lt;a href="https://vuex.feathersjs.com/getting-started.html" rel="noopener noreferrer"&gt;setup process&lt;/a&gt; and key concepts, such as the &lt;a href="https://vuex.feathersjs.com/auth-plugin.html" rel="noopener noreferrer"&gt;Auth Plugin&lt;/a&gt;, the &lt;a href="https://vuex.feathersjs.com/service-plugin.html" rel="noopener noreferrer"&gt;Service Plugin&lt;/a&gt;, and &lt;a href="https://vuex.feathersjs.com/model-classes.html" rel="noopener noreferrer"&gt;Data Modeling&lt;/a&gt;. While this series will cover concepts relevant to the use-cases described, it will not be possible to cover everything. &lt;/p&gt;

&lt;h4&gt;
  
  
  Dashboard
&lt;/h4&gt;

&lt;p&gt;To demonstrate the continuous stream of data, I'll be creating a simple dashboard filled with charts. &lt;/p&gt;

&lt;h4&gt;
  
  
  Creating a Dashboard View
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;// /src/views/Dashboard.vue
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"home container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Dashboard&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Messages Sent&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;BarChart&lt;/span&gt; &lt;span class="na"&gt;:chart-data=&lt;/span&gt;&lt;span class="s"&gt;"barchartdata"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"options"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Active Chat Rooms&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;BarChart&lt;/span&gt; &lt;span class="na"&gt;:chart-data=&lt;/span&gt;&lt;span class="s"&gt;"barchartdata2"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"options"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Recent Logins&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;LineChart&lt;/span&gt; &lt;span class="na"&gt;:chart-data=&lt;/span&gt;&lt;span class="s"&gt;"linechartdata"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"options"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Current Tasks&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DonutChart&lt;/span&gt; &lt;span class="na"&gt;:chart-data=&lt;/span&gt;&lt;span class="s"&gt;"donutchartdata"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"doptions"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;DEBUG&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;serverMessage&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&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;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice chart components added to this dashboard view. We'll create these down below.&lt;/p&gt;

&lt;h4&gt;
  
  
  Adding the View to the Routes
&lt;/h4&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;routes&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&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;/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Chat&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;/dashboard&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;Dashboard&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;Dashboard&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;h4&gt;
  
  
  Adding a Dashboard Link to the Chat View
&lt;/h4&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-wrapper block center-element"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
     &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt;
     &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://feathersjs.com/img/feathers-logo-wide.png"&lt;/span&gt;
     &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Feathers Logo"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Chat&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"float-right link"&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Dashboard
&lt;span class="nt"&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Displaying the Data
&lt;/h4&gt;

&lt;p&gt;To visualize the flow of data, we'll use charts to display data updates. I'm going to use the vue wrapper library &lt;a href="https://vue-chartjs.org/" rel="noopener noreferrer"&gt;vue-chartjs&lt;/a&gt; for &lt;a href="https://www.chartjs.org/" rel="noopener noreferrer"&gt;Charts.js&lt;/a&gt;, which provides a simple yet customizable charting library. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn add vue-chartjs chart.js&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating Chart Components
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;vue-chartjs&lt;/code&gt; makes it easy to add charts as a chart component within a single vue component file. View the &lt;a href="https://vue-chartjs.org/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to learn more about how it can be used within a vue app.&lt;/p&gt;

&lt;p&gt;Here is an example of the bar chart component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;// /src/components/BarChart.vue
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&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;Bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mixins&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;vue-chartjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;reactiveProp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mixins&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="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;reactiveProp&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;props&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;chartData&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;options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nf"&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="nf"&gt;renderChart&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;chartData&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;options&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you include the &lt;code&gt;mixins&lt;/code&gt; and &lt;code&gt;reactiveProp&lt;/code&gt;. The reactiveProp mixin adds a watcher to the &lt;code&gt;chartData&lt;/code&gt; variable, enabling updates as data changes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Listening to Events
&lt;/h4&gt;

&lt;p&gt;To create an event listener for the &lt;code&gt;dataAvailable&lt;/code&gt; event, add an event listener when the Dashboard component gets &lt;code&gt;mounted()&lt;/code&gt;, and remove the event listener when the Dashboard component gets &lt;code&gt;destroyed()&lt;/code&gt;. Take a look at the code below to see how the event listener is created:&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="nf"&gt;mounted&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 an event listener to dataAvailable event&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;establishConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;destroyed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// remove the dataAvailable event listener&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroyConnection&lt;/span&gt;&lt;span class="p"&gt;();&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="nf"&gt;destroyConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;feathersClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dataAvailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;establishConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;feathersClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dataAvailable&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;data&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="nf"&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;Receiving data from server: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="c1"&gt;// update variables to the data received from the server&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;messageCount&lt;/span&gt; &lt;span class="o"&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;messageCount&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;recentLogins&lt;/span&gt; &lt;span class="o"&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;recentLogins&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;activeChatRooms&lt;/span&gt; &lt;span class="o"&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;activeChatRooms&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;openTickets&lt;/span&gt; &lt;span class="o"&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;openTickets&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;closedTickets&lt;/span&gt; &lt;span class="o"&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;closedTickets&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;unassignedTickets&lt;/span&gt; &lt;span class="o"&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;unassignedTickets&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;serverMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;Now, when you run the vue app and navigate to the &lt;code&gt;/dashboard&lt;/code&gt; page, you should see the charts updating every 5 seconds. &lt;/p&gt;

&lt;h3&gt;
  
  
  Check Your Work
&lt;/h3&gt;

&lt;p&gt;The final code up to this point is on the &lt;code&gt;hello-world&lt;/code&gt; branch of this repo: &lt;a href="https://github.com/meditatingdragon/realtime-feathers/tree/hello-world" rel="noopener noreferrer"&gt;https://github.com/meditatingdragon/realtime-feathers/tree/hello-world&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level Up: Create a Custom Metrics Service
&lt;/h2&gt;

&lt;p&gt;Go beyond Hello World and create a &lt;a href="https://docs.feathersjs.com/guides/basics/services.html#customizing-a-service" rel="noopener noreferrer"&gt;custom service&lt;/a&gt; that delivers the data. Feathers provides an easy way to &lt;a href="https://docs.feathersjs.com/guides/basics/services.html#generating-a-service" rel="noopener noreferrer"&gt;generate a service&lt;/a&gt; for an application feature. For our dashboard, we can create a &lt;code&gt;MetricsService&lt;/code&gt;.&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;feathers&lt;/span&gt; &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? What kind of service is it? A custom service
? What is the name of the service? metrics
? Which path should the service be registered on? /metrics
? Does the service require authentication? No
   create src/services/metrics/metrics.service.js
    force src/services/index.js
   create src/services/metrics/metrics.class.js
   create src/services/metrics/metrics.hooks.js
   create test/services/metrics.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define the MetricsService as a custom service that can create data every 5 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getRandomInt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../utils/dataGenerator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* eslint-disable no-unused-vars */&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Metrics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&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;logins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nf"&gt;setInterval&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sending new data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&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="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;messageCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;activeChatRooms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;recentLogins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;openTickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;closedTickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;unassignedTickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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="mi"&gt;5000&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 we can update our data connection to use the service:&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="nf"&gt;establishConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;feathersClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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="nf"&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;Receiving data from server: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="c1"&gt;// update variables to the data received from the server&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;messageCount&lt;/span&gt; &lt;span class="o"&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;messageCount&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;recentLogins&lt;/span&gt; &lt;span class="o"&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;recentLogins&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;activeChatRooms&lt;/span&gt; &lt;span class="o"&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;activeChatRooms&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;openTickets&lt;/span&gt; &lt;span class="o"&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;openTickets&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;closedTickets&lt;/span&gt; &lt;span class="o"&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;closedTickets&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;unassignedTickets&lt;/span&gt; &lt;span class="o"&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;unassignedTickets&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;serverMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;h3&gt;
  
  
  Check Your Work
&lt;/h3&gt;

&lt;p&gt;The final code up to this point is on the &lt;code&gt;metrics-service&lt;/code&gt; branch of this repo: &lt;a href="https://github.com/meditatingdragon/realtime-feathers/tree/metrics-service" rel="noopener noreferrer"&gt;https://github.com/meditatingdragon/realtime-feathers/tree/metrics-service&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next: Channels
&lt;/h2&gt;

&lt;p&gt;To handle real-time events in future posts, we'll be making use of &lt;a href="https://docs.feathersjs.com/api/channels.html#concepts" rel="noopener noreferrer"&gt;channels&lt;/a&gt;. If you want to get a jump start, take a look at the docs.&lt;/p&gt;

&lt;p&gt;Let me know - how will you leverage real-time events in your application?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vue</category>
      <category>feathersjs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Create Beautiful Transactional Emails with the Feathers-Mailer Module</title>
      <dc:creator>Rachel</dc:creator>
      <pubDate>Wed, 28 Oct 2020 12:57:25 +0000</pubDate>
      <link>https://dev.to/rachel_cheuk/create-beautiful-transactional-emails-with-the-feathers-mailer-module-3ll</link>
      <guid>https://dev.to/rachel_cheuk/create-beautiful-transactional-emails-with-the-feathers-mailer-module-3ll</guid>
      <description>&lt;p&gt;&lt;a href="https://feathersjs.com/" rel="noopener noreferrer"&gt;FeathersJS&lt;/a&gt; is a robust web application framework for real-time applications and REST APIs. It's great for serving as a Minimum Viable Product (MVP) backend and scales when you are ready to move beyond MVP stage and grow your customer base. &lt;/p&gt;

&lt;p&gt;Feathers provides a &lt;a href="https://docs.feathersjs.com/guides/basics/starting.html#installing-feathers" rel="noopener noreferrer"&gt;command-line tool&lt;/a&gt; to quickly scaffold a project and a handful of generators to build out services to meet your project needs. &lt;/p&gt;

&lt;p&gt;One of the first features I developed with Feathers is transactional emails. &lt;/p&gt;

&lt;p&gt;This article covers creating beautiful templated emails - even if you don't have design skills. By leveraging a combination of several tools, it's quite easy to develop professional emails for your platform.&lt;/p&gt;

&lt;p&gt;The codebase for this tutorial will be building off a previous post I wrote - &lt;a href="https://dev.to/rachel_cheuk/intro-user-roles-and-management-with-quasar-feathersjs-mongodb-4hk5"&gt;Intro: Fullstack JS User Roles &amp;amp; Management&lt;/a&gt;. At the end of this series, the email functionality already exists as part of account management and registration, but the emails are boring text emails like &lt;code&gt;Click link here.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this article, I'll use a combination of the following to transform the text emails into beautiful branded emails: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/feathersjs-ecosystem/feathers-mailer" rel="noopener noreferrer"&gt;feathers-mailer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reallygoodemails.com/" rel="noopener noreferrer"&gt;Really Good Emails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://html-to-pug.com/" rel="noopener noreferrer"&gt;html-to-pug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pugjs.org/" rel="noopener noreferrer"&gt;Pug&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Template
&lt;/h3&gt;

&lt;p&gt;Beginning with the &lt;a href="https://github.com/meditatingdragon/quasar-feathersjs-user-management" rel="noopener noreferrer"&gt;User Roles &amp;amp; Management Repo&lt;/a&gt;, I created a new repo using it as the template. You're welcome to follow along if you'd like by &lt;a href="https://github.blog/2019-06-06-generate-new-repositories-with-repository-templates/" rel="noopener noreferrer"&gt;creating a copy&lt;/a&gt; of the above repo. The final repo is located &lt;a href="https://github.com/meditatingdragon/feathers-mailer-emails" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This repo already includes functionality for user login, password reset, and account registration, all of which send out an email when the action is triggered. I won't go into too much detail in this article, but if you'd like to learn more, read my &lt;a href="https://dev.to/rachel_cheuk/intro-user-roles-and-management-with-quasar-feathersjs-mongodb-4hk5"&gt;previous article&lt;/a&gt; that covers it in greater detail.&lt;/p&gt;

&lt;h4&gt;
  
  
  Project Features
&lt;/h4&gt;

&lt;p&gt;This project leverages &lt;a href="https://github.com/feathersjs-ecosystem/feathers-authentication-management" rel="noopener noreferrer"&gt;feathers-authentication-management&lt;/a&gt; to provide account management functionality. An email service is also created to send out emails. &lt;/p&gt;

&lt;h4&gt;
  
  
  Email Service
&lt;/h4&gt;

&lt;p&gt;The email service is quite simple. I set it up following the instructions found in the &lt;a href="https://github.com/feathersjs-ecosystem/feathers-mailer" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;feathers generate service&lt;/code&gt; with the name 'email'. &lt;/p&gt;

&lt;p&gt;This scaffolds out a service named email. I then defined the mailer configuration in the service 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="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="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Initialize our service with any options it requires&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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="nc"&gt;Mailer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;smtpTransport&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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_host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;secure&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="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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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_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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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_pw&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="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;code&gt;app.get('variable')&lt;/code&gt; function pulls the value from a configuration file. Details about this can be found in the &lt;a href="https://docs.feathersjs.com/api/configuration.html#configuration" rel="noopener noreferrer"&gt;FeathersJS configuration docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The email service uses the feathers-mailer module, which is a wrapper for the &lt;a href="https://nodemailer.com/" rel="noopener noreferrer"&gt;nodemailer&lt;/a&gt; library so it supports the same transport options. For this project, I used the &lt;a href="https://nodemailer.com/transports/ses/" rel="noopener noreferrer"&gt;AWS SES transport&lt;/a&gt;, but you can also configure your service with different supported &lt;a href="https://nodemailer.com/transports/" rel="noopener noreferrer"&gt;transport options&lt;/a&gt; or &lt;a href="https://nodemailer.com/smtp/#general-options" rel="noopener noreferrer"&gt;SMTP options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To configure for AWS, the following configuration keys will be needed from your AWS account:&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;"smtp_user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_smtp_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;"smtp_pw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_smtp_pw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"smtp_host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_smtp_host"&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;You can add these to the &lt;code&gt;${env}.json&lt;/code&gt; configuration file or &lt;code&gt;default.json&lt;/code&gt; configuration file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sending Emails
&lt;/h4&gt;

&lt;p&gt;Feathers-authentication-management allows you to define which emails are sent based on the action requested. There's a total of 6 actions: resendVerifySignup, verifySignup, sendResetPwd, resetPwd, passwordChange, and identityChange. &lt;/p&gt;

&lt;p&gt;These are all defined in the &lt;a href="https://github.com/meditatingdragon/feathers-mailer-emails/blob/master/server/src/services/auth-management/notifier.js" rel="noopener noreferrer"&gt;notifier function&lt;/a&gt;, which is passed to the authManagement service. Here's an excerpt of the notifier function for the Send Password Reset action:&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;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="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// generates the token link&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hash&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="o"&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;?token=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hash&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;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// sends the email using an email service&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nf"&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;Sent 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;result&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="nf"&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;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="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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FROM_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;from_email&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tokenLink&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;email&lt;/span&gt;&lt;span class="p"&gt;;&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;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// user clicks link from email to verify their email&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendResetPwd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;tokenLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset-password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;email&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="nx"&gt;FROM_EMAIL&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;user&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="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;Reset Password&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;html&amp;gt;&amp;lt;b&amp;gt;Reset Password&amp;lt;/b&amp;gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tokenLink&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/html&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;return&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&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="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 notifier function is where we'll want to send the branded emails we'll soon be creating. &lt;/p&gt;

&lt;h3&gt;
  
  
  Transforming Emails
&lt;/h3&gt;

&lt;p&gt;Up to this point, the code simply sends a simple HTML string with some content. We want it to do more. We want it to send a beautiful HTML email that is personalized for the user. To generate this HTML, we'll use a templating language so we can insert variables and allow customization on a per user basis. &lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://pugjs.org/api/getting-started.html" rel="noopener noreferrer"&gt;Pug&lt;/a&gt;, but you're welcome to use a different templating language if you prefer. The template will compile to HTML, which you can then send as the email where we currently provide a text HTML string. &lt;/p&gt;

&lt;p&gt;Within the server directory, install Pug:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm i pug --save&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Pug is a bit more concise than HTML, eliminating the need for code blocks. It relies on declaring the element type before providing the content. &lt;/p&gt;

&lt;p&gt;Take a look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p Welcome #{name}! You're now registered for #{event}.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the template is compiled, you'll get something like this:&lt;br&gt;
&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;p&amp;gt;&lt;/span&gt;Welcome Sarah! You're now registered for Fullstack Javascript.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use code blocks to generate HTML tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-
  var list = ["Apples", "Bananas", "Oranges",
          "Kiwis", "Jackfruit", "Kumquats"]
each item in list
  li= item
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which results in:&lt;br&gt;
&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;li&amp;gt;&lt;/span&gt;Apples&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Bananas&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Oranges&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Kiwis&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Jack&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Kumquats&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take a look at the &lt;a href="https://pugjs.org/api/getting-started.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt; to see it's full features.&lt;/p&gt;

&lt;h4&gt;
  
  
  Email Templates
&lt;/h4&gt;

&lt;p&gt;While not required, I used email templates found on &lt;a href="https://reallygoodemails.com/" rel="noopener noreferrer"&gt;Really Good Emails&lt;/a&gt; to reduce the design time. I can easily tweak a design I like and match the colors/brands I want to use. They sort email templates into categories so you can easily search emails matching a template you want to use, from &lt;a href="https://reallygoodemails.com/categories/giveaway" rel="noopener noreferrer"&gt;giveaways&lt;/a&gt; to &lt;a href="https://reallygoodemails.com/categories/account" rel="noopener noreferrer"&gt;account setup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you find an email you like, you can also modify it live on their site before exporting to use elsewhere. &lt;/p&gt;

&lt;p&gt;For this project, I'll use a simple &lt;a href="https://reallygoodemails.com/emails/reset-your-password-2/live" rel="noopener noreferrer"&gt;Password Reset Template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxqv31ywe7va110lcbqxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxqv31ywe7va110lcbqxw.png" alt="Screenshot of Really Good Emails" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML to Pug
&lt;/h4&gt;

&lt;p&gt;Once I'm done creating a template according to my brand, I can use another tool to compile the HTML to Pug, called &lt;a href="https://html-to-pug.com/" rel="noopener noreferrer"&gt;HTML-to-Pug&lt;/a&gt;. Copy/paste HTML on one side, get Pug outputted on the other side. Quick and easy! It's not perfect, but it does most of the heavy lifting for generating the Pug code needed: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5y27jpgu4fqwuuqk8854.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5y27jpgu4fqwuuqk8854.png" alt="Image of HTML to Pug" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I save the Pug code into a new template file directly nested under the auth-management folder. You'll find the pug code under &lt;code&gt;/server/src/services/auth-management/templates/password-reset.pug&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now I can add some customization once I have the Pug template code.&lt;/p&gt;

&lt;p&gt;For this project, I'm keeping it simple. I want to change the headline to include the user's name, and I'll add in the reset link. So if I reset my own account password, I'll see &lt;code&gt;Reset your password Rachel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I updated the pug template to include the firstname variable and the url link for the button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reset your password #{firstname}
...
...
a.Button-primary(href=url  ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variables are then provided to the HTML compile function, so the new notifier code looks 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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendResetPwd&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;tokenLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset-password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// create the function to compile the pug template to HTML&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pwReset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compileFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates&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;password-reset.pug&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;email&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="nx"&gt;FROM_EMAIL&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;user&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="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;Reset Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// use the function and add in the variables required by the template. This will output customized HTML.&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pwReset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tokenLink&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;sendEmail&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when I send a request for a password reset, I receive the following email:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fngeex50qcennmuxhj4fk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fngeex50qcennmuxhj4fk.png" alt="Screenshot of Customized Password Reset Email" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can then repeat this process for other transactional emails, or even create an automated email campaign.&lt;/p&gt;

&lt;p&gt;Now it's your turn. What will you create? &lt;/p&gt;

&lt;h4&gt;
  
  
  Resource List
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.feathersjs.com/guides/" rel="noopener noreferrer"&gt;FeathersJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/feathersjs-ecosystem/feathers-mailer" rel="noopener noreferrer"&gt;feathers-mailer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reallygoodemails.com/" rel="noopener noreferrer"&gt;Really Good Emails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://html-to-pug.com/" rel="noopener noreferrer"&gt;html-to-pug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pugjs.org/" rel="noopener noreferrer"&gt;Pug&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Did I Miss Something?
&lt;/h4&gt;

&lt;p&gt;Leave a comment and let me know how this works for you. If something isn't clear, ask.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>feathersjs</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
