<?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>Forem: Rukshan J. Senanayaka</title>
    <description>The latest articles on Forem by Rukshan J. Senanayaka (@rukshanjs).</description>
    <link>https://forem.com/rukshanjs</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%2F892162%2F8e44578d-2b22-4cd8-84d8-7d05a60b0779.jpeg</url>
      <title>Forem: Rukshan J. Senanayaka</title>
      <link>https://forem.com/rukshanjs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rukshanjs"/>
    <language>en</language>
    <item>
      <title>I Fine-Tuned Gemma 4 for LaTeX OCR. The Success Was the Problem.</title>
      <dc:creator>Rukshan J. Senanayaka</dc:creator>
      <pubDate>Tue, 21 Apr 2026 16:12:23 +0000</pubDate>
      <link>https://forem.com/rukshanjs/i-fine-tuned-gemma-4-for-latex-ocr-the-success-was-the-problem-4ehi</link>
      <guid>https://forem.com/rukshanjs/i-fine-tuned-gemma-4-for-latex-ocr-the-success-was-the-problem-4ehi</guid>
      <description>&lt;p&gt;&lt;em&gt;A fine-tuning post-mortem, and three tests that showed me what my model actually learned.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today I fine-tuned Google's &lt;code&gt;gemma-4-E2B-it&lt;/code&gt; on the &lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt; dataset using LoRA on a RunPod RTX 3090. Nine hours of training, about $2 in GPU cost, and an adapter uploaded to Hugging Face.&lt;/p&gt;

&lt;p&gt;The training loss dropped from &lt;strong&gt;13.66 to 0.018&lt;/strong&gt;. On the test set, the outputs were near-perfect.&lt;/p&gt;

&lt;p&gt;Then I ran three tests that, taken together, told me exactly what the model had, and hadn't, learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An image from the training dataset&lt;/strong&gt;: correct output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same image, with only the colors changed&lt;/strong&gt; (white-on-blue instead of black-on-white): notation went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A handwritten equation&lt;/strong&gt;: complete hallucination. The model invented Hebrew letters and fractions that weren't there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting lesson wasn't any one of those tests. It was &lt;strong&gt;how the model failed worse and worse&lt;/strong&gt; as the input moved further from its training data.&lt;/p&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base model&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;unsloth/gemma-4-E2B-it&lt;/code&gt; (vision-language, ~9.5 GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt; (image → LaTeX pairs, 68.7k rows)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method&lt;/td&gt;
&lt;td&gt;LoRA (rank 8, alpha 8, all linear layers, vision + language)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;RunPod, RTX 3090 (24 GB VRAM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Unsloth Studio + TRL SFTTrainer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Epochs&lt;/td&gt;
&lt;td&gt;1.0 (8,586 steps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch size&lt;/td&gt;
&lt;td&gt;8 (effective 8, no gradient accumulation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak LR&lt;/td&gt;
&lt;td&gt;2.0e-4, linear decay, 5-step warmup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision&lt;/td&gt;
&lt;td&gt;bf16 (not 4-bit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training time&lt;/td&gt;
&lt;td&gt;8h 53m, ~$1.96&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Final adapter size: &lt;strong&gt;58 MB&lt;/strong&gt;. That's the whole point of LoRA: a tiny, portable delta instead of a new 9 GB model.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Training Looked Like
&lt;/h2&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%2Fuploads%2Farticles%2Fcojh9v5yj9qpi6sg3a2z.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%2Fuploads%2Farticles%2Fcojh9v5yj9qpi6sg3a2z.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Training loss behaved the way you hope for: fast descent, then a long tail.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Train Loss&lt;/th&gt;
&lt;th&gt;Eval Loss&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;13.66&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;0.94&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;0.17&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;859 (10%)&lt;/td&gt;
&lt;td&gt;~0.13&lt;/td&gt;
&lt;td&gt;2.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4,295 (50%)&lt;/td&gt;
&lt;td&gt;~0.04&lt;/td&gt;
&lt;td&gt;2.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6,013 (70%)&lt;/td&gt;
&lt;td&gt;~0.02&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.14 (best)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8,586 (100%)&lt;/td&gt;
&lt;td&gt;0.018&lt;/td&gt;
&lt;td&gt;2.18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Final training loss: &lt;strong&gt;0.018&lt;/strong&gt;. Best eval loss: &lt;strong&gt;2.14&lt;/strong&gt; at step 6,013.&lt;/p&gt;

&lt;p&gt;If you stop reading here you'd conclude the model learned the task. That's what I concluded.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem I Missed Until I Ran Real Inference
&lt;/h2&gt;

&lt;p&gt;Look at eval loss more carefully.&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%2Fuploads%2Farticles%2F8z2x6v8wjavbav67m2et.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%2Fuploads%2Farticles%2F8z2x6v8wjavbav67m2et.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 859 (10% through training): eval loss 2.21&lt;/li&gt;
&lt;li&gt;Step 8,586 (100% through training): eval loss 2.18&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eval loss barely moved for 90% of training. Meanwhile training loss went from 0.13 to 0.018. That gap is overfitting. Best eval loss was at step 6,013. The final checkpoint is &lt;em&gt;worse&lt;/em&gt; than an earlier one.&lt;/p&gt;

&lt;p&gt;I read this as "I should have used early stopping" and moved on. The real lesson was bigger, and showed up only when I actually tested the model on images.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Tests, Getting Worse
&lt;/h2&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%2Fuploads%2Farticles%2F9qzmk6mynu4ttyh3rbxy.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%2Fuploads%2Farticles%2F9qzmk6mynu4ttyh3rbxy.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Three tests, one dataset image as the starting point. Each column shows the input, the model's LaTeX output, and what that output actually renders as. How the model breaks down, step by step, is the whole story.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: An image from the training set
&lt;/h3&gt;

&lt;p&gt;I grabbed what's shown as the first row in the &lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt; dataset viewer:&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%2Fuploads%2Farticles%2Fhtddacqo49hupc3w21ng.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%2Fuploads%2Farticles%2Fhtddacqo49hupc3w21ng.png" alt=" " width="160" height="40"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt: Transcribe the LaTeX from this image.
Output: \frac{N}{M} \in \mathbf{Z} , \frac{M}{P} \in \mathbf{Z} , \frac{P}{Q} \in \mathbf{Z}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, valid LaTeX. &lt;code&gt;\mathbf{Z}&lt;/code&gt; is the modern equivalent of &lt;code&gt;\bf Z&lt;/code&gt;, which is &lt;em&gt;exactly&lt;/em&gt; how the dataset labels this image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training label: { \frac { N } { M } } \in { \bf Z } , { \frac { M } { P } } \in { \bf Z } , { \frac { P } { Q } } \in { \bf Z }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verdict: correct.&lt;/strong&gt; But almost certainly because this image was &lt;em&gt;in the training set&lt;/em&gt;. With 1 epoch on 68.7k examples and visible overfitting in the loss curve, the model has seen this image and learned to reproduce its label. High performance on the training distribution is not evidence the task was learned; it's often evidence the distribution was memorized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: The same image, only the colors changed
&lt;/h3&gt;

&lt;p&gt;I took the same image and did one thing: linearly remapped pixel colors so that black became white and white became a clear blue. Anti-aliased edges preserved exactly. No content, resolution, font, or layout changes.&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%2Fuploads%2Farticles%2F77p4wuu1jwxahdbsoiw4.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%2Fuploads%2Farticles%2F77p4wuu1jwxahdbsoiw4.png" alt=" " width="160" height="40"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt: Transcribe the LaTeX from this image.
Output: \frac{N}{M} \in \mathbb{Z}, \frac{M}{P} \in $\mathbb{Z}$, $\frac{P}{Q}$ \in $\mathbb{Z}$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verdict: malformed LaTeX.&lt;/strong&gt; The fractions and &lt;code&gt;\in&lt;/code&gt; are still there, but look at those stray &lt;code&gt;$&lt;/code&gt; signs. &lt;code&gt;$&lt;/code&gt; is a math-mode delimiter in LaTeX: it closes math mode when you're in it and opens it when you're in text. Scattered mid-expression like this, they break the parser. If you fed this output to a LaTeX compiler, the &lt;code&gt;\frac{P}{Q}&lt;/code&gt; inside &lt;code&gt;$...$&lt;/code&gt; gets treated as text, the &lt;code&gt;\mathbb{Z}&lt;/code&gt; outside math mode fails to render correctly, and the decoded image (see the visual above) comes out visibly broken.&lt;/p&gt;

&lt;p&gt;Nothing changed except color. The adapter's learned mapping &lt;code&gt;(image pattern → LaTeX token)&lt;/code&gt; partially collapsed. The model went from confident math-mode prediction on the white version to inserting &lt;code&gt;$&lt;/code&gt; delimiters as if it's uncertain whether it's in math or text mode, because the pixel-statistics that anchored its learned mapping aren't there anymore.&lt;/p&gt;

&lt;p&gt;There's also a notation shift: on white bg the model produces &lt;code&gt;\mathbf{Z}&lt;/code&gt; (matching training labels). On blue bg it outputs &lt;code&gt;\mathbb{Z}&lt;/code&gt; (the blackboard-bold that the original image &lt;em&gt;actually&lt;/em&gt; shows, but which &lt;em&gt;doesn't&lt;/em&gt; appear in the training labels). The model is pulling tokens from a different part of its vocabulary on this input.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;shortcut learning&lt;/strong&gt;: the adapter latched onto the easiest signal that correlated with labels on the training set (pixel brightness statistics) instead of the right signal (glyph shapes). Flip the brightness, and the shortcut stops firing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 3: A handwritten equation
&lt;/h3&gt;

&lt;p&gt;I snapped a photo of a handwritten fraction and fed it in.&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%2Fuploads%2Farticles%2F2bapohb4r860noh1kl8k.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%2Fuploads%2Farticles%2F2bapohb4r860noh1kl8k.png" alt=" " width="160" height="40"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt: Transcribe the LaTeX from this image.
Output: \frac { \aleph } { \mathcal{M}} \ \in \ \zeta , \ \frac { \mathcal{M}} { \varphi } \ \in \ \zeta , \ \frac { \mathcal{L}} { \mathcal{Q} } \ \in \ \zeta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verdict: complete hallucination.&lt;/strong&gt; The output has nothing to do with the image. The model invents Hebrew letters (&lt;code&gt;\aleph&lt;/code&gt;), script letters (&lt;code&gt;\mathcal{M}, \mathcal{L}, \mathcal{Q}&lt;/code&gt;), Greek (&lt;code&gt;\zeta, \varphi&lt;/code&gt;), and hallucinates a three-fraction structure that isn't in the picture at all.&lt;/p&gt;

&lt;p&gt;The adapter has no mapping for this visual input. It falls back on the surface-level shape of its training outputs ("this is usually about three fractions joined by &lt;code&gt;\in&lt;/code&gt;") and produces plausible-looking LaTeX that is entirely decoupled from what's in the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pattern
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Distribution shift&lt;/th&gt;
&lt;th&gt;Output quality&lt;/th&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Training-set image&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Correct (matches training label)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same content, blue bg&lt;/td&gt;
&lt;td&gt;Colors only&lt;/td&gt;
&lt;td&gt;Malformed LaTeX, wrong notation&lt;/td&gt;
&lt;td&gt;Shortcut on pixel stats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handwritten&lt;/td&gt;
&lt;td&gt;Everything about rendering&lt;/td&gt;
&lt;td&gt;Total nonsense&lt;/td&gt;
&lt;td&gt;Domain collapse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The failure mode gets worse as distribution shift increases. There's no clean line between "works" and "doesn't work". The model gets worse in steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this actually shows
&lt;/h3&gt;

&lt;p&gt;The model didn't learn &lt;em&gt;"recognize LaTeX symbols."&lt;/em&gt; It learned &lt;em&gt;"when I see images that look like &lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt;'s rendering style, produce outputs that look like &lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt;'s labels."&lt;/em&gt; Move the image away from that style, and the output degrades in a predictable way: first it loses confidence in its math-mode markers (and starts inserting &lt;code&gt;$&lt;/code&gt; delimiters mid-expression), then notation drifts, then structure breaks completely and it hallucinates.&lt;/p&gt;

&lt;p&gt;Eval loss never caught any of this. The eval split comes from the same narrow distribution as training, so it only measures generalization &lt;em&gt;within&lt;/em&gt; that narrow world. A stable eval loss can mask the fact that the model is learning shortcuts that will fall apart the moment conditions change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Eval loss tells you if you're overfitting the training set. It doesn't tell you if you're overfitting the dataset itself.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Early stopping at step 2,000 wouldn't have fixed any of this. It's not a training-duration problem. It's a dataset-diversity and missing-augmentation problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  One More Thing That Almost Fooled Me: The Prompt
&lt;/h2&gt;

&lt;p&gt;Before I understood the pattern above, I had one more thing to rule out.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt; dataset has no "prompt" column, only &lt;code&gt;image&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt;. So how does the model know what instruction it's responding to?&lt;/p&gt;

&lt;p&gt;Unsloth Studio auto-generates one. It inspects the dataset name and content, matches a task pattern, and wraps every training example with a default instruction. For &lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt;, that instruction is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Convert this image to LaTeX notation."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(In the &lt;code&gt;unslothai/unsloth&lt;/code&gt; repo, at &lt;code&gt;/studio/backend/utils/datasets/vlm_processing.py&lt;/code&gt;, line 79. Unsloth Studio's code auto-matches the "latex" task pattern via dataset-name and content heuristics.)&lt;/p&gt;

&lt;p&gt;My inference code used &lt;em&gt;"Transcribe the LaTeX from this image."&lt;/em&gt;, a different prompt. That one-line difference pushes the adapter off-distribution on the text side, independent of any image-side issues. The stray &lt;code&gt;$&lt;/code&gt; delimiters on Test 2, the hallucinated &lt;code&gt;\aleph&lt;/code&gt; and &lt;code&gt;\zeta&lt;/code&gt; on Test 3, even some of the notation drift: all of these are likely worse than they'd be under the correct prompt.&lt;/p&gt;

&lt;p&gt;But here's the thing. I used the &lt;strong&gt;same wrong prompt for all three tests&lt;/strong&gt;. That means the prompt is held constant across Test 1, Test 2, and Test 3. If the prompt were the dominant cause of failure, Test 1 (the dataset image) should fail too. It doesn't: Test 1 produces clean, correct LaTeX that matches the training label exactly.&lt;/p&gt;

&lt;p&gt;So the variable that differs between working and broken output is not the prompt. It's the image. The image-side failure is real regardless of the prompt. The prompt choice changes the exact shape of the failure but not whether it happens.&lt;/p&gt;

&lt;p&gt;The lesson generalizes: &lt;strong&gt;the prompt is part of the training distribution.&lt;/strong&gt; An adapter is tuned for specific &lt;code&gt;(image, instruction)&lt;/code&gt; pairs; change the instruction and you've shifted the distribution. Prompt mismatch looks exactly like a model weakness (degraded output, inconsistent notation, odd hallucinations) when really you're just asking a question the model wasn't trained to answer.&lt;/p&gt;

&lt;p&gt;Practical rule: before testing a fine-tuned adapter, find out what instruction it was trained with and use that exact string at inference. For automated tools like Unsloth Studio, check the preprocessing code. For custom scripts, check your own training loop. Never assume.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Numbers Actually Told Me (In Hindsight)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The overfitting curve
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Progress&lt;/th&gt;
&lt;th&gt;Train Loss&lt;/th&gt;
&lt;th&gt;Eval Loss&lt;/th&gt;
&lt;th&gt;What this meant&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;~0.13&lt;/td&gt;
&lt;td&gt;2.21&lt;/td&gt;
&lt;td&gt;Real learning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;~0.04&lt;/td&gt;
&lt;td&gt;2.15&lt;/td&gt;
&lt;td&gt;Marginal gains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;~0.02&lt;/td&gt;
&lt;td&gt;2.14&lt;/td&gt;
&lt;td&gt;Best generalization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0.018&lt;/td&gt;
&lt;td&gt;2.18&lt;/td&gt;
&lt;td&gt;Eval got &lt;em&gt;worse&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The cost of not knowing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Steps&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Cost ($0.22/hr)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What I did&lt;/td&gt;
&lt;td&gt;8,586&lt;/td&gt;
&lt;td&gt;8h 54m&lt;/td&gt;
&lt;td&gt;~$1.96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop at best eval&lt;/td&gt;
&lt;td&gt;6,013&lt;/td&gt;
&lt;td&gt;~6h 13m&lt;/td&gt;
&lt;td&gt;~$1.37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggressive early stop&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;~2h 4m&lt;/td&gt;
&lt;td&gt;~$0.46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2K + packing + 4-bit&lt;/td&gt;
&lt;td&gt;~2,000&lt;/td&gt;
&lt;td&gt;~1h (est.)&lt;/td&gt;
&lt;td&gt;~$0.22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nearly the same quality model, same &lt;em&gt;memorization&lt;/em&gt; of the same narrow distribution, roughly &lt;strong&gt;9x cheaper&lt;/strong&gt; if I'd known what to watch for. Note: "same quality" here means same behavior on the dataset. The handwriting hallucination would be identical regardless of training length; that failure isn't solvable by spending more hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Watch eval loss and stop when it plateaus
&lt;/h3&gt;

&lt;p&gt;Configure early stopping, or at least evaluate more frequently than every 10% of training. Eval had stopped improving by ~step 2,000. I trained four times longer than needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Don't trust eval loss alone. Run out-of-distribution inference &lt;em&gt;during&lt;/em&gt; training
&lt;/h3&gt;

&lt;p&gt;Mid-training, grab a few images deliberately &lt;em&gt;outside&lt;/em&gt; your dataset style and run inference. If those fail while eval loss looks fine, your dataset is too narrow. The three tests I ran at the end are what I should have been running every few thousand steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Diversify the dataset
&lt;/h3&gt;

&lt;p&gt;One narrow dataset → one narrow model. For a real LaTeX OCR model I'd mix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unsloth/LaTeX_OCR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Images rendered with different LaTeX engines, fonts, DPIs&lt;/li&gt;
&lt;li&gt;Scanned or photographed equations with noise and skew&lt;/li&gt;
&lt;li&gt;A small handwritten sample&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Augment aggressively
&lt;/h3&gt;

&lt;p&gt;A single line of data augmentation would have forced the model to learn &lt;em&gt;shape&lt;/em&gt; instead of &lt;em&gt;brightness&lt;/em&gt;. Standard in computer vision, not on by default in most LLM fine-tuning pipelines. For vision-language tasks, at minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Random color inversion and channel swap&lt;/li&gt;
&lt;li&gt;Brightness and contrast jitter&lt;/li&gt;
&lt;li&gt;Small rotations (±5°) and scale jitter&lt;/li&gt;
&lt;li&gt;Gaussian noise and JPEG compression artifacts&lt;/li&gt;
&lt;li&gt;Random background color / hue shift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any one of these would have prevented the Test 2 failure. Together they turn a narrow dataset into something meaningfully broader.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Enable 4-bit quantization and packing
&lt;/h3&gt;

&lt;p&gt;I trained in bf16 with packing disabled. Both are free wins for most LoRA setups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;QLoRA (4-bit)&lt;/strong&gt; roughly halves VRAM use with negligible quality impact for LoRA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packing&lt;/strong&gt; concatenates short sequences into one batch, cutting padding waste. For variable-length LaTeX strings, likely 1.5–2x faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Larger effective batch size
&lt;/h3&gt;

&lt;p&gt;With 4-bit + packing there's room to push batch size from 8 to 16 or 32. Fewer steps, more stable gradients.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Use a longer warmup
&lt;/h3&gt;

&lt;p&gt;My warmup was 5 steps, which is about 0.06% of total training. The grad norm peaked at 19.6 on step 1. That kind of early spike is what warmup is supposed to absorb, so next run I'd use a few hundred warmup steps (~1–5% of total) instead. A cosine decay schedule is a common alternative to linear and might train more smoothly, but I didn't test it on this run, so I can't say either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Save fewer, better checkpoints
&lt;/h3&gt;

&lt;p&gt;Saving every 30 steps produced 288 checkpoints, ~34 GB of disk. Every 200–500 is plenty.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Deploy the &lt;em&gt;best&lt;/em&gt; checkpoint, not the last one
&lt;/h3&gt;

&lt;p&gt;Step 6,013 (eval 2.14) is what I'd actually use. The reflex of "load the final checkpoint" is wrong whenever training overfits, and for LoRA on narrow data, that's most of the time.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Always match the inference prompt to training
&lt;/h3&gt;

&lt;p&gt;Before using the adapter, find the exact instruction the dataset was trained with. Save it next to the model files. Never guess.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Training loss always goes down. That's gravity, not a signal.&lt;/strong&gt; Eval loss is only meaningful if your eval set represents what you'll actually use the model for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success on training-style images is not evidence of learning.&lt;/strong&gt; It's often evidence of memorization. Test at &lt;em&gt;different levels of difficulty&lt;/em&gt; (in-distribution, small shift, large shift) and watch how the output degrades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure happens in steps, not all at once.&lt;/strong&gt; Small shifts produce wrong notation. Bigger shifts produce hallucination. How it breaks tells you what shortcut the model learned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a one-variable stress test.&lt;/strong&gt; Change only one thing (color, bg, font) in a training image. If output changes, the model is tracking that variable as a shortcut.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The prompt is part of the training distribution.&lt;/strong&gt; Find the training prompt, use it verbatim at inference. Prompt mismatch is silent distribution shift and looks exactly like a bad model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The dataset's &lt;em&gt;styling&lt;/em&gt; becomes load-bearing.&lt;/strong&gt; Color, contrast, resolution, rendering: all get baked in unless augmentation forces otherwise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning cost is mostly avoidable.&lt;/strong&gt; Early stopping + packing + 4-bit can cut a run ~9x with no meaningful quality loss. But "quality" here means "behavior on the training distribution." Out-of-distribution failure isn't something more training fixes.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gemma4</category>
      <category>unsloth</category>
      <category>nlp</category>
    </item>
    <item>
      <title>DX_1: How we created an Optimized Search for our Faculty's Career Fair Website</title>
      <dc:creator>Rukshan J. Senanayaka</dc:creator>
      <pubDate>Mon, 19 Feb 2024 07:50:14 +0000</pubDate>
      <link>https://forem.com/rukshanjs/dx1-how-we-created-an-optimized-search-for-our-facultys-career-fair-website-1o44</link>
      <guid>https://forem.com/rukshanjs/dx1-how-we-created-an-optimized-search-for-our-facultys-career-fair-website-1o44</guid>
      <description>&lt;p&gt;Last Update - 19th Feb, 2024 | 1347&lt;/p&gt;

&lt;h2&gt;
  
  
  DX_ Series
&lt;/h2&gt;

&lt;p&gt;This is an article of my &lt;code&gt;DX_&lt;/code&gt; series where I post my developer experience one article at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yr_X91Wr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://res.cloudinary.com/dlzrie4qh/image/upload/v1708327902/RJS_Shared/UoM/t9lxeehdyp8wcpqfzlvi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yr_X91Wr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://res.cloudinary.com/dlzrie4qh/image/upload/v1708327902/RJS_Shared/UoM/t9lxeehdyp8wcpqfzlvi.gif" alt="Your Alt Text" width="600" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The annual career fair organized by our &lt;a href="https://uom.lk/itfac"&gt;Faculty of IT, University of Moratuwa&lt;/a&gt; stands as a vital bridge connecting students with top industry professionals. 2024 FIT Future Careers Career fair was held on 8th of February,2024.&lt;/p&gt;

&lt;p&gt;Recognizing the need to enhance this connection, the student body &lt;a href="http://intecs.itfac.mrt.ac.lk/"&gt;INTECS&lt;/a&gt; responsible for the event has launched a dedicated website. However, the site's search feature initially fell short of expectations. &lt;/p&gt;

&lt;p&gt;In this article, I'll share how I tackled these challenges to refine the search function, ensuring the website effectively matches the best talents with the greatest opportunities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was the problem?
&lt;/h2&gt;

&lt;p&gt;Upon joining the career fair website team, my main focus was on deployment. However, I quickly identified a major flaw in the website's search functionality. &lt;/p&gt;

&lt;p&gt;Originally designed to support 300 students, the site inefficiently fetched all student records in one API call for every load of the search page, performing the filtering on the client side. &lt;/p&gt;

&lt;p&gt;This not only strained the website's performance but also risked overloading the API server, given our limited server resources. For instance, if even if the user views only the first page of the results, all 300 student details are already loaded, which is a huge overhead.&lt;/p&gt;

&lt;p&gt;We faced a critical need for a more efficient search solution that balanced functionality with our budget constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  My thought pattern
&lt;/h2&gt;

&lt;p&gt;Facing the search functionality issues on our career fair website, I recalled using &lt;a href="https://www.elastic.co/"&gt;Elasticsearch&lt;/a&gt; in a past project for efficient search and filtering. This experience inspired me to apply a similar solution, but cost was a concern due to our tight budget. &lt;/p&gt;

&lt;p&gt;I explored open-source options, finding &lt;a href="https://github.com/typesense/typesense"&gt;Typesense&lt;/a&gt;, a cost-effective, typo-tolerant search engine offering fast, relevant search capabilities and a managed cloud service with a free tier. &lt;/p&gt;

&lt;p&gt;Despite initially considering self-hosting, their managed cloud service - &lt;a href="https://cloud.typesense.org/"&gt;Typesense Cloud&lt;/a&gt; proved to be the most practical choice, allowing us to enhance search efficiency without financial burden or extensive setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little bit of problematic implementation
&lt;/h2&gt;

&lt;p&gt;Earlier implementation looked something like this. &lt;/p&gt;

&lt;p&gt;You can see when the &lt;code&gt;Student.jsx&lt;/code&gt; component of the student search page is loaded, it loads all the users from the database via the API, which is not good.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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;axios&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="s2"&gt;`user/get-all-users`&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setAllUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;The data returned from the API endpoint are then used to update the state variable &lt;code&gt;allUsers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;allUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setAllUsers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The users were filtered using a simple JS &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"&gt;&lt;code&gt;filter()&lt;/code&gt;&lt;/a&gt; method. This frontend filter doesn't do much other than just simply filtering the already-retrieved database data. &lt;/p&gt;

&lt;p&gt;This doesn't provide the performance improvements a dedicated search system would provide, such as typo-tolerance etc. multiple search criteria etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filterUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedAreas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;f_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;l_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;isNameMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;preferredAreas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;preferredAreas&lt;/span&gt; &lt;span class="o"&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;parse&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;preferred_area&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="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;e&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error parsing preferred_area:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&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;isAreaMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selectedAreas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;preferredAreas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;selectedAreas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&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;isNameMatch&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isAreaMatch&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;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;To improve the search functionality without changing the UI, I chose Typesense for its high performance and React compatibility. Typesense offers fast, typo-tolerant searches, ideal for instant feedback on user queries. &lt;/p&gt;

&lt;p&gt;I integrated it using &lt;a href="https://www.npmjs.com/package/react-instantsearch"&gt;React InstantSearch&lt;/a&gt; components &amp;amp; hooks, adapting them for Typesense to enhance search without altering the UI. The setup involved initializing a Typesense server for the search index, facilitated by Typesense’s clear documentation. &lt;/p&gt;

&lt;p&gt;This allowed for a smooth transition to Typesense’s optimized search components, ensuring easy customization and adaptability to our UI needs. Also automatic pagination and filter components are provided with this library.&lt;/p&gt;

&lt;p&gt;A Node.js program was developed to create the indexes of the fields using which we needed to filter the students contained the schema definitions as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define schema for the 'students' collection&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&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="s2"&gt;students&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;num_documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uni_index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optional&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="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="s2"&gt;f_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optional&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="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="s2"&gt;l_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optional&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="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="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optional&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="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="s2"&gt;preferred_area&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string[]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&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;optional&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="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="s2"&gt;skills&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string[]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&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;optional&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="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="s2"&gt;cv_link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optional&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="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="s2"&gt;front_picture_link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;facet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optional&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="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 following is how the indexes look in the managed cloud server.&lt;/p&gt;

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

&lt;p&gt;Then in the react frontend application, we consumed the search and configured which fields and the weights we need to assign to each field during the searching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typesenseAdapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypesenseInstantsearchAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TYPESENSE_SERVER_CONFIG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;additionalSearchParameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queryBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f_name,l_name,skills,preferred_area,uni_index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;query_by_weights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3,3,2,2,1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;numTypos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;typoTokensThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tackled the challenge of integrating Typesense’s search components without altering the existing design by customizing React InstantSearch components to match the application's aesthetic. &lt;/p&gt;

&lt;p&gt;By applying custom styles and utilizing modified hooks like a debounced useSearchBox and an adapted useHits, I ensured the search functionality blended seamlessly and efficiently with the UI. &lt;/p&gt;

&lt;p&gt;This resulted in an enhanced user search experience—faster and more accurate—without disrupting the design continuity. The successful integration underscored my ability to improve functionality while maintaining the visual integrity of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who keeps the search index updated? - Cron!
&lt;/h2&gt;

&lt;p&gt;The above mentioned Node.js application was deployed as a Cronb job to automate updating the Typesense search index, ensuring our database records were consistently synchronized for reliable search functionality. &lt;/p&gt;

&lt;p&gt;This application, designed to run as a cron job, leverages Node.js's strengths in asynchronous operations and database compatibility, making it an efficient solution for data indexing. &lt;/p&gt;

&lt;p&gt;It periodically (every 15 minutes) checks for new or modified database entries, formats them for Typesense, and uploads them to the search server on managed Typesense instance. &lt;/p&gt;

&lt;p&gt;Deployed as a &lt;a href="https://docs.digitalocean.com/products/functions/"&gt;serverless function on Digital Ocean&lt;/a&gt; using &lt;a href="https://github.com/mrbrianevans/do-functions"&gt;&lt;code&gt;do-functions&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;do-functions-server&lt;/code&gt; packages, this setup streamlines the indexing process, maintaining the search feature's efficiency and reliability. Otherwise even if the students update their details and save to database, those updated details would not be returned in search results without being indexed.&lt;/p&gt;

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

&lt;p&gt;In this instalment of DX_ series, I shared how we upgraded our faculty career fair website's search function by shifting from basic front-end filtering to an advanced, type-ahead search with Typesense. &lt;/p&gt;

&lt;p&gt;This cost-effective improvement was achieved by integrating Typesense's search engine and automating data indexing through a custom Node.js &lt;a href="https://en.wikipedia.org/wiki/Cron"&gt;cron&lt;/a&gt; job. &lt;/p&gt;

&lt;p&gt;The result was a significant enhancement in connecting students with employers, without altering the user interface, demonstrating the impact of open-source solutions and innovation in addressing project challenges and user requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/typesense/typesense-instantsearch-demo"&gt;https://github.com/typesense/typesense-instantsearch-demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.typesense.org/"&gt;https://cloud.typesense.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lucene.apache.org/core/"&gt;https://lucene.apache.org/core/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/products/app-platform"&gt;https://www.digitalocean.com/products/app-platform&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  BONUS: Tech stack for the whole project
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Frontend - React.js&lt;/li&gt;
&lt;li&gt;Backend - Node.js&lt;/li&gt;
&lt;li&gt;Database - MySQL&lt;/li&gt;
&lt;li&gt;Search - Typesense&lt;/li&gt;
&lt;li&gt;Image storage - Firebase storage&lt;/li&gt;
&lt;li&gt;User tour - driver.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Although I contributed to the search feature, I was just one member of the whole project. The whole project was a successful effort of many undergraduates of the Faculty from different batches.&lt;/li&gt;
&lt;li&gt;Code snippets and screenshots in this article may not be production-ready. They are edited as needed for educational purposes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>algolia</category>
      <category>search</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Part 3/3 - How to create a server-side timer using WebSockets (with Socket.IO), NestJS and Flutter</title>
      <dc:creator>Rukshan J. Senanayaka</dc:creator>
      <pubDate>Mon, 15 Aug 2022 15:30:00 +0000</pubDate>
      <link>https://forem.com/rukshanjs/part-33-how-to-create-a-server-side-timer-using-websockets-with-socketio-nestjs-and-flutter-10am</link>
      <guid>https://forem.com/rukshanjs/part-33-how-to-create-a-server-side-timer-using-websockets-with-socketio-nestjs-and-flutter-10am</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this part 3/3, I will show you how to implement WebSocket functionality in a new Flutter app and connect it to the NestJS backend server application that we implemented in part 2/3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code segments in this article are written for reading purpose, the full source code is available on my GitHub, linked at the end.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a new flutter project
&lt;/h2&gt;

&lt;p&gt;I’m using Flutter 2.5.2 with null safety. A later version should support as well, but didn’t test with a newer version. &lt;/p&gt;

&lt;p&gt;You can create a new Flutter project by executing following command.&lt;/p&gt;

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

flutter create server_timer_frontend


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

&lt;/div&gt;

&lt;p&gt;This can take a couple of minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Install Socket.IO client package for Flutter
&lt;/h2&gt;

&lt;p&gt;After your Flutter project is created, we can add the package of the socket client we are going to use. &lt;/p&gt;

&lt;p&gt;I have tested several packages for this purpose. It was very difficulty to find a proper one because for our WebSocket connection to work successfully, both the backend Socket.IO version and the frontend client Socket.IO version &lt;strong&gt;must&lt;/strong&gt; be compatible. So I had to do some trial and error and some online research to find out the best package.&lt;/p&gt;

&lt;p&gt;The best package I found is named &lt;strong&gt;socket_io_client&lt;/strong&gt; and its latest update (version 2.0.0) was released a few weeks ago (July of 2022). You can see more info at &lt;a href="https://pub.dev/packages/socket_io_client" rel="noopener noreferrer"&gt;https://pub.dev/packages/socket_io_client&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;You can install it by running&lt;/p&gt;

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

flutter pub add socket_io_client


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

&lt;/div&gt;

&lt;p&gt;One side note - the official Flutter documentation shows a way to implement WebSocket connection using a package called &lt;strong&gt;web_socket_channel&lt;/strong&gt;, it was not working for me with my NestJS backend.&lt;/p&gt;

&lt;p&gt;Before writing code, create a new folder called &lt;strong&gt;sockets&lt;/strong&gt; within the &lt;strong&gt;lib&lt;/strong&gt; folder, where we will put all of our code related to sockets. You can refactor them as however you like later.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create socket service (socket_service.dart)
&lt;/h2&gt;

&lt;p&gt;In the frontend, we have to have several files related to this implementation. One important file is &lt;strong&gt;socket_service.dart&lt;/strong&gt;. Here we define methods such as &lt;code&gt;connectAndListenToSocket(){}&lt;/code&gt;, &lt;code&gt;disconnectSocket(){}&lt;/code&gt;, &lt;code&gt;disposeSocket(){}&lt;/code&gt; which are important for our WebSocket communication.&lt;/p&gt;

&lt;p&gt;The following is the abstract structure of the &lt;code&gt;SocketService&lt;/code&gt; class.&lt;/p&gt;

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

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SocketService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Socket&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// connectAndListenToSocket(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// disconnectSocket(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// disposeSocket(){}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;First, write a method named &lt;code&gt;connectAndListenToSocket(String authToken, String deviceId){}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, within the &lt;code&gt;connectAndListenToSocket(){}&lt;/code&gt; method, we have to set up the connection to the backend. Since the backend requires a user auth token, we have to send that as well with this. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please edit the IP address according to your network connection. Otherwise this will not connect to the NestJS backend. You can view your IP from network settings of your computer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The default port of the NestJS app is 3000. That is written in right side of the semicolon (&lt;code&gt;:&lt;/code&gt;) as in the following code. (Replace the 192.168.X.X with your actual IP address)&lt;/p&gt;

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

    &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;'http://192.168.X.X:3000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Replace with your network IP &lt;/span&gt;
        &lt;span class="n"&gt;OptionBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTransports&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'websocket'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="s"&gt;'token'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Bearer &lt;/span&gt;&lt;span class="si"&gt;$authToken&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="s"&gt;'deviceId'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disableAutoConnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It takes two parameters, the user &lt;code&gt;authToken&lt;/code&gt; (you can get this from Firebase Auth or any other auth method), the &lt;code&gt;deviceId&lt;/code&gt; (you can get this by using &lt;strong&gt;device_info_plus&lt;/strong&gt; package (&lt;a href="https://pub.dev/packages/device_info_plus" rel="noopener noreferrer"&gt;https://pub.dev/packages/device_info_plus&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;setQuery()&lt;/code&gt; method to send query parameters in the body of our message. This body can be caught by using the &lt;code&gt;@MessageBody()&lt;/code&gt; decorator in NestJS.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;connectAndListenToSocket(){}&lt;/code&gt; method will try to connect the socket to backend. &lt;/p&gt;

&lt;p&gt;Next, write the following piece of code which tries to actually start the WebSocket connection if it is not yet started.&lt;/p&gt;

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

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'connecting....'&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;Next, inside this &lt;code&gt;connectAndListenToSocket(){}&lt;/code&gt; method, we can set up some listeners which are for main events such as &lt;code&gt;onConnect(){}&lt;/code&gt;, &lt;code&gt;onDisconnect(){}&lt;/code&gt;, &lt;code&gt;onError(){}&lt;/code&gt; and &lt;code&gt;onConnectError(){}&lt;/code&gt; etc. as below.&lt;/p&gt;

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

    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;onConnect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'connected and listening to socket!.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;onDisconnect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'disconnected from socket!.'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;onConnectError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We can listen in the frontend, to the custom events set by us in the backend. Therefore, whenever the backend server sends a message under that event, this listener would be fired.&lt;/p&gt;

&lt;p&gt;Next, write the following listener which listens to the &lt;code&gt;tick&lt;/code&gt; event of our server-side timer app. We defined this event in the backend NestJS application.&lt;/p&gt;

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

    &lt;span class="c1"&gt;// When the message event 'tick' received from server, that data is added to a stream 'streamSocket'.&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimerEvents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tick&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;streamSocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'timer'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In that listener, we add the received data to a Flutter &lt;code&gt;StreamController&lt;/code&gt; using the &lt;code&gt;streamSocket.addResponse(data["timer"].toString());&lt;/code&gt; method call. The variable &lt;code&gt;data&lt;/code&gt; is the payload that we send from the backend. The value of the key &lt;code&gt;timer&lt;/code&gt; contains the actual time in seconds.&lt;/p&gt;




&lt;p&gt;Finally write the two functions for disconnecting the socket and disposing the socket.&lt;/p&gt;

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

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;disconnectSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;disposeSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;dispose&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 difference between the two is, &lt;code&gt;disconnectSocket(){}&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; remove the listeners created in our Flutter app for the WebSocket connection, but the &lt;code&gt;disposeSocket(){}&lt;/code&gt; does.&lt;/p&gt;

&lt;p&gt;If we connect to the same socket again, the multiple listeners would be present if we had only disconnected, and not disposed the WebSocket connection. Therefore, to start everything fresh next time, it's a good idea to &lt;code&gt;disposeSocket(){}&lt;/code&gt; the socket when need to finish the WebSocket connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Create timer service (timer_service.dart)
&lt;/h2&gt;

&lt;p&gt;In this service file, I will define the methods &lt;code&gt;startServerTimer&lt;/code&gt; and &lt;code&gt;stopServerTimer&lt;/code&gt;.&lt;/p&gt;

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

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimerService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// startServerTimer(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// stopServerTimer(){}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Whenever we want to send something to the backend, we use the &lt;code&gt;socket.emit()&lt;/code&gt; method call. This method call would accept a first string parameter - the name of the event, and then the second parameter is the data to be sent as a JSON object. &lt;/p&gt;

&lt;p&gt;We can accept this JSON object in our backend easily - using the &lt;code&gt;@MessageBody()&lt;/code&gt; decorator in NestJS.&lt;/p&gt;

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

  &lt;span class="c1"&gt;// Timer methods.&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;startServerTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimerEvents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timerStart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"dur"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;duration&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No socket connection found."&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;static&lt;/span&gt; &lt;span class="n"&gt;stopServerTimer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimerEvents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timerStop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No socket connection found."&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;h2&gt;
  
  
  5. Stream socket (stream_socket.dart)
&lt;/h2&gt;

&lt;p&gt;This file is not essential for the socket implementation but I’m using it to store data received from the WebSocket connection and use it to update the UI easily using a &lt;code&gt;StreamBuilder&lt;/code&gt; Flutter widget in my countdown timer example.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamSocket&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamController&lt;/span&gt; &lt;span class="n"&gt;_socketResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamController&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;addResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_socketResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sink&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;getResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_socketResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asBroadcastStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;StreamSocket&lt;/span&gt; &lt;span class="n"&gt;streamSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamSocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Of course there can be many other alternative ways to do this part. I would love to hear them in the comments below.&lt;/p&gt;

&lt;p&gt;Don’t forget to import this file inside the &lt;strong&gt;socket_service.dart&lt;/strong&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Events file
&lt;/h2&gt;

&lt;p&gt;In the flutter side also, I’m maintaining a &lt;strong&gt;socket_events.dart&lt;/strong&gt; file to keep the needed socket events as enums, just like we did in NestJS in the part 2/3 of this tutorial series.&lt;/p&gt;

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

&lt;span class="kt"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;TimerEvents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;timerStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;timerStop&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;Don’t forget to import this file inside the &lt;strong&gt;socket_service.dart&lt;/strong&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Add the UI to display socket data (main.dart)
&lt;/h2&gt;

&lt;p&gt;The abstract structure of the &lt;strong&gt;main.dart&lt;/strong&gt; is as follows.&lt;/p&gt;

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

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Setup MaterialApp&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyHomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Initiate the state.&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyHomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// _convertToDisplayTime(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// initState(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// build(){}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;First, clear out the comments from the default Flutter application.&lt;/p&gt;

&lt;p&gt;Next, the important thing is, we have to write code inside the &lt;code&gt;_MyHomePageState(){}&lt;/code&gt; class. Since the code is a bit long, I'll mention only the important things here, because you can get the full source code from the GitHub link at the end of the article.&lt;/p&gt;

&lt;p&gt;We declare following function to convert the data from our backend to proper timer format for display.&lt;/p&gt;

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

  &lt;span class="c1"&gt;// This is to convert the time in seconds to a string with the format '00:00'.&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;_convertToDisplayTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;twoDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;padLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;twoDigitMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twoDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inMinutes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remainder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;twoDigitSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twoDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inSeconds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remainder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inHours&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$twoDigitMinutes&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$twoDigitSeconds&lt;/span&gt;&lt;span class="s"&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="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${twoDigits(duration.inHours)}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$twoDigitMinutes&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$twoDigitSeconds&lt;/span&gt;&lt;span class="s"&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;When the WebSocket connection is established and connected, we are rendering a &lt;code&gt;StreamBuilder&lt;/code&gt; widget on screen to display the data from that connection using the &lt;strong&gt;stream_socket.dart&lt;/strong&gt; file we created earlier.&lt;/p&gt;

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

            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;StreamBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;stream:&lt;/span&gt; &lt;span class="n"&gt;streamSocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Socket Status : TICKING TIMEOUT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="s"&gt;'Socket Status : TIMER TICKING - &lt;/span&gt;&lt;span class="si"&gt;${_convertToDisplayTime(
                              Duration(
                                seconds: int.parse(snapshot.data.toString()),
                              ),
                            ).toString()}&lt;/span&gt;&lt;span class="s"&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;connected&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Socket Status : CONNECTED"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Socket Status : DISCONNECTED"&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="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Socket Status : DISCONNECTED'&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 have to create the buttons which are there for the functions such as connect socket, disconnect, dispose, start timer and stop timer.&lt;/p&gt;




&lt;p&gt;Here is the &lt;strong&gt;Connect&lt;/strong&gt; button code.&lt;/p&gt;

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

            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Populate these two values with your own values.&lt;/span&gt;
                &lt;span class="c1"&gt;// Get token from Firebase Auth or other authentication service.&lt;/span&gt;
                &lt;span class="c1"&gt;// Get deviceId using `device_info_plus` package.&lt;/span&gt;
                &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectAndListenToSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'deviceId'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Start a socket channel with the server.&lt;/span&gt;

                &lt;span class="c1"&gt;// Update the UI when anything change in the socket.&lt;/span&gt;
                &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;onAny&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="n"&gt;setState&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="nl"&gt;label:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Connect Socket'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect_without_contact_rounded&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;primary:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;purple&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;Here are the &lt;strong&gt;Start timer&lt;/strong&gt; and &lt;strong&gt;Stop timer&lt;/strong&gt; buttons code.&lt;/p&gt;

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

            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;TimerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startServerTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Start the server-side timer with 10 seconds.&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Start Server Countdown'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;primary:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;green&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;900&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="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;TimerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stopServerTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Stop Server Countdown'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timer_off&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;primary:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;900&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;Here we are just calling the methods defined in &lt;code&gt;TimerService&lt;/code&gt; to start a timer on the NestJS server or to stop it.&lt;/p&gt;




&lt;p&gt;The disconnect socket does not remove the listeners, but dispose does. You can see the code for the relevant &lt;strong&gt;Disconnect socket&lt;/strong&gt; and &lt;strong&gt;Dispose socket&lt;/strong&gt; buttons below.&lt;/p&gt;

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

            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Disconnect from the socket. (Does not remove the listeners.)&lt;/span&gt;
                &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disconnectSocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Disconnect Socket&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;(Does not remove the listeners)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove_done&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;primary:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;purple&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="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Disconnect the socket and remove the listeners.&lt;/span&gt;
                &lt;span class="n"&gt;SocketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disposeSocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Dispose Socket&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;(Fresh Start - removes all listeners)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;primary:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;purple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;That is all about the frontend UI part of the code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Common errors
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Error 1
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebSocketException: Connection to '&lt;a href="http://192.168.X.X:3000/socket.io/?deviceId=deviceId&amp;amp;EIO=4&amp;amp;transport=websocket#" rel="noopener noreferrer"&gt;http://192.168.X.X:3000/socket.io/?deviceId=deviceId&amp;amp;EIO=4&amp;amp;transport=websocket#&lt;/a&gt;' was not upgraded to websocket&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Make sure you have correctly set the `SocketsGateway` as a **provider** in **app.module.ts** in the NestJS backend.

- Error 2

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

&lt;/div&gt;



&lt;p&gt;SocketException: OS Error: Network is unreachable, errno = 101, address = 192.168.X.X, port = XXXXX&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Make sure you are connected to the correct network connection and that you are using the correct IP.

## Conclusion

That’s it. We have completed the flutter frontend implementation of WebSocket communication.

Hooray! 🥳🎉🎉🎉🥳🥳🥳🎉🥳🎉🎉 You've reached this far! Give yourself a pat on the back - you deserve it. 

I hope now you have some idea on how to implement a server-side timer using WebSockets(with Socket.IO), NestJS and Flutter. There may have been alternative ways to do some of the things that I have done in this tutorial series and I would love to hear about them in the comments section below.

Also, don't hesitate to point out any mistakes that I have made because I'm also learning this stuff! 

Please share your solution as well if you follow this series and make your own - I would love to see it. Have a wonderful day - Peace out ✌️

## Support me!

Do you think I deserve a cup of coffee for this article? 😃

&amp;lt;a href="https://www.buymeacoffee.com/rukshanjs" target="_blank"&amp;gt;&amp;lt;img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" &amp;gt;&amp;lt;/a&amp;gt;

## Video

[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/kx7LQNNmTX4/0.jpg)](https://www.youtube.com/watch?v=kx7LQNNmTX4)

## Source code

You can find the full source code of the project at my GitHub, https://github.com/RukshanJS/websockets-nestjs-flutter

## References

1. socket_io_client connection issue - https://stackoverflow.com/q/71679446
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>websockets</category>
      <category>flutter</category>
      <category>nestjs</category>
      <category>realtime</category>
    </item>
    <item>
      <title>Part 2/3 - How to create a server-side timer using WebSockets (with Socket.IO), NestJS and Flutter</title>
      <dc:creator>Rukshan J. Senanayaka</dc:creator>
      <pubDate>Mon, 15 Aug 2022 15:30:00 +0000</pubDate>
      <link>https://forem.com/rukshanjs/part-23-how-to-create-a-server-side-timer-using-websockets-with-socketio-nestjs-and-flutter-4o3n</link>
      <guid>https://forem.com/rukshanjs/part-23-how-to-create-a-server-side-timer-using-websockets-with-socketio-nestjs-and-flutter-4o3n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this part (part 2/3), I will discuss how to implement the backend server application using NestJS to establish a WebSocket connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code segments in this article are written for reading purpose, the full source code is available on my GitHub, linked at the end.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NestJS is a wonderful backend framework to develop mid to large scale projects. It supports WebSockets officially and a very straightforward setup is available to get things going. &lt;/p&gt;

&lt;p&gt;I am considering a single stateful server of NestJS and I will avoid &lt;strong&gt;Redis&lt;/strong&gt; and &lt;strong&gt;microservices&lt;/strong&gt; for now but they would be advisable to use for a fully-scalable application, to keep track of users connected to each WebSocket connection.&lt;/p&gt;

&lt;p&gt;I mentioned this stateful because to keep track of all the connected users and their timers, a single variable called &lt;code&gt;userTimers&lt;/code&gt; is maintained in our backend app.&lt;/p&gt;

&lt;p&gt;That means if the server shuts down for some reason, that state would be lost and we would loose the timers for the users. If we use Redis and microservices knowledge, we could avoid this problem. But I think that should be for another topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstract architecture of our server-side timer app
&lt;/h2&gt;

&lt;p&gt;The following diagram gives an abstract idea of the system we are going to implement. The method calls in the diagram are the ones usually used in a WebSocket connection. If you don't understand it fully, please go through this tutorial series again after reading all 3 parts for the first time. I have tried to simplify things as much as possible.&lt;/p&gt;

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

&lt;p&gt;The things that we have to do for the backend implementation are the following steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start a new NestJS project
&lt;/h2&gt;

&lt;p&gt;Please install NestJS on your computer if you don't have already, by running the following command.&lt;/p&gt;

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

npm i -g @nestjs/cli


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

&lt;/div&gt;

&lt;p&gt;Then create a new NestJS project.&lt;/p&gt;

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

nest new server-timer-backend


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  2. Install the needed packages
&lt;/h2&gt;

&lt;p&gt;The two packages that we require are provided by NestJS developers. Since I’ve tested this setup with NestJS version 8.0.0 (Although the latest version is 9), I’ll be using an earlier, supported version of the required packages. This setup is working as of early August, 2022.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@nestjs/websockets@8.4.7&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@nestjs/platform-socket.io@8.4.7&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can install both of them by running the following command in the terminal. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npm i --save @nestjs/websockets@8.4.7 @nestjs/platform-socket.io@8.4.7


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  3. Create a sockets gateway (sockets.gateway.ts)
&lt;/h2&gt;

&lt;p&gt;Firstly, I’ll create a folder named &lt;strong&gt;sockets&lt;/strong&gt; where we will keep all of our files required for WebSocket communication for now. You can refactor these files in anyway you like. &lt;/p&gt;

&lt;p&gt;Create a new file called &lt;strong&gt;sockets.gateway.ts&lt;/strong&gt;. &lt;/p&gt;



&lt;p&gt;Here the gateway file can be thought of as the main file in the backend related to WebSocket communication. It handles the &lt;strong&gt;connection&lt;/strong&gt;, &lt;strong&gt;disconnection&lt;/strong&gt; and any other &lt;strong&gt;message event&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This &lt;code&gt;SocketsGateway&lt;/code&gt; class implements two interfaces which are provided by the &lt;strong&gt;@nestjs/websockets&lt;/strong&gt; package.&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;@WebSocketServer()&lt;/code&gt; decorator provided by the above package to tell NestJS to inject the WebSocket server.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;WebSocketGateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SocketsGateway&lt;/span&gt;
  &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnGatewayConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnGatewayDisconnect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// handleConnection(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// handleDisconnect(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// startMyTimer(){}&lt;/span&gt;

  &lt;span class="c1"&gt;// stopMyTimer(){}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;




&lt;p&gt;I mentioned that an agreed-upon communication channel has to be created between client and server to start WebSocket communication. This agreement is established via a &lt;strong&gt;handshake&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Whenever this handshake is established, the &lt;code&gt;handleConnection(){}&lt;/code&gt; method is invoked. This handshake is a real thing and its details can be accessed via &lt;code&gt;client.handshake&lt;/code&gt; as a JSON with various information related to this WebSocket connection.&lt;/p&gt;

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

  &lt;span class="nf"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;ConnectedSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// console.log(&lt;/span&gt;
    &lt;span class="c1"&gt;//   `user ${client.user.id} with socket ${client.id} connected with device ${client.handshake?.query?.deviceId}`,&lt;/span&gt;
    &lt;span class="c1"&gt;// );&lt;/span&gt;

    &lt;span class="nx"&gt;client&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="nf"&gt;getUserDeviceRoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;client&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;




&lt;p&gt;&lt;code&gt;handleDisconnect(){}&lt;/code&gt; is more or less similar and it is invoked when an already connected socket is disconnected. You still have access to the handshake via &lt;code&gt;client.handshake&lt;/code&gt;.&lt;/p&gt;

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

  &lt;span class="nf"&gt;handleDisconnect&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;ConnectedSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// console.log(&lt;/span&gt;
    &lt;span class="c1"&gt;//   `user ${client.user.id} with socket ${client.id} with device ${client.handshake?.query?.deviceId} DISCONNECTED`,&lt;/span&gt;
    &lt;span class="c1"&gt;// );&lt;/span&gt;

    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;getUserDeviceRoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;client&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;A message event is a user-defined event to which the server &lt;strong&gt;listens&lt;/strong&gt; (or &lt;strong&gt;subscribes&lt;/strong&gt;). When a client (for example our flutter app) sends a message under that event, the server will execute whatever code is inside this listener method for that particular message event.&lt;/p&gt;

&lt;p&gt;We are using two message events in our timer app - &lt;code&gt;timerStart&lt;/code&gt; and &lt;code&gt;timerStop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;startMyTimer(){}&lt;/code&gt; method, I use the &lt;code&gt;@MessageBody()&lt;/code&gt; decorator to catch the payload sent from the client to server via the WebSocket connection. Here, the duration of the timer to be started in seconds, is contained in &lt;code&gt;body.dur&lt;/code&gt; value. (We set this in our Flutter app)&lt;/p&gt;

&lt;p&gt;In this example, I stop any existing timer for a user device before starting a new timer for that device.&lt;/p&gt;

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

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;SubscribeMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TimerEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timerStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nf"&gt;startMyTimer&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;ConnectedSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;MessageBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Stop any existing timer for this user device.&lt;/span&gt;
    &lt;span class="nf"&gt;stopTimerForUserDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;client&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Start a new timer for this user device.&lt;/span&gt;
    &lt;span class="nf"&gt;startTimerForUserDevice&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;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;client&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Timer duration&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;SubscribeMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TimerEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timerStop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nf"&gt;stopMyTimer&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;ConnectedSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Stop current timer for this user device.&lt;/span&gt;
    &lt;span class="nf"&gt;stopTimerForUserDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;client&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;h2&gt;
  
  
  4. Create a socket adapter (authenticated-socket.adapter.ts)
&lt;/h2&gt;

&lt;p&gt;When we use WebSockets, we want to only allow authenticated users to connect with our backend WebSocket service. For that, we have to have a custom socket adapter (although one is given to us by default). This will make sure each and every request to establish a WebSocket connection will be made by a user authenticated in your backend. &lt;/p&gt;



&lt;p&gt;First, create a class named &lt;code&gt;AuthenticatedSocketAdapter&lt;/code&gt; which extends the &lt;code&gt;IoAdapter&lt;/code&gt; from &lt;code&gt;@nestjs/platform-socket.io&lt;/code&gt; package as below.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthenticatedSocketAdapter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;IoAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// constructor(){}&lt;/span&gt;

   &lt;span class="c1"&gt;// createIOServer(){}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Next, write the constructor for this class. Here, if you have an &lt;strong&gt;AuthService&lt;/strong&gt; implemented using Firebase Auth or any other auth service of your choice, just uncomment the commented lines shown in the code snippet below. &lt;/p&gt;

&lt;p&gt;This authentication can be done using Firebase Auth or any other auth service of your choice and it doesn’t matter, as long as it will throw an exception for an unauthorized user. If the user is authorized, the WebSocket connection is established and if not, it is terminated by throwing &lt;code&gt;Authentication error&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;If you don't have such an implementation yet, don't worry just leave these as is - we can test the implementation without authentication also.&lt;/p&gt;

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

  &lt;span class="c1"&gt;// private readonly authService:AuthService;&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&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;INestApplicationContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&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="c1"&gt;// this.authService = this.app.get(AuthService);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We'll cover authentication in another tutorial so that the WebSocket connections will only be available to authenticated users.&lt;/p&gt;




&lt;p&gt;Next, create a method named &lt;code&gt;createIOServer()&lt;/code&gt;. Inside it, we are writing a middleware to grab the auth token from the handshake of the WebSocket connection, and authenticate that token using an &lt;strong&gt;AuthService&lt;/strong&gt; (AuthService file is not implemented in this tutorial - if you don't have one yourself, you can ignore it for now because we don't need authentication for testing).&lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;token&lt;/code&gt; is valid, the AuthService should return the relevant &lt;code&gt;user&lt;/code&gt; from the &lt;code&gt;authenticateToken()&lt;/code&gt; method. This returned &lt;code&gt;user&lt;/code&gt; value is assigned to the &lt;code&gt;socket.user&lt;/code&gt; variable. It is because we do this, we are able to access the relevant user as &lt;code&gt;client.user&lt;/code&gt; by using the &lt;code&gt;@ConnectedSocket()&lt;/code&gt; decorator in the &lt;strong&gt;sockets.gateway.ts&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;If the token is invalid, it will throw an error and the WebSocket connection would not be established.&lt;/p&gt;

&lt;p&gt;Here also, if you have an &lt;strong&gt;AuthService&lt;/strong&gt; implemented using Firebase Auth or any other auth service of your choice, just uncomment the commented lines shown in the code snippet below.&lt;/p&gt;

&lt;p&gt;Please note that I have imported the &lt;code&gt;Server&lt;/code&gt; type from &lt;code&gt;socket.io&lt;/code&gt; library to our application as,&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Server&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;socket.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This is how I have imported &lt;code&gt;Server&lt;/code&gt; type in &lt;strong&gt;sockets.gateway.ts&lt;/strong&gt;, &lt;strong&gt;rooms.ts&lt;/strong&gt;, &lt;strong&gt;events.ts&lt;/strong&gt; files as well.&lt;/p&gt;

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

  &lt;span class="nf"&gt;createIOServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="kr"&gt;any&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;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIOServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&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="nx"&gt;server&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;tokenPayload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenPayload&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;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Token not provided&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid authentication method. Only Bearer is supported.&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
        &lt;span class="c1"&gt;// const user = await this.authService.authenticateToken(token);&lt;/span&gt;
        &lt;span class="c1"&gt;// socket.user = user;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authentication error&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;server&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;h2&gt;
  
  
  5. Enable WebSockets in main.ts
&lt;/h2&gt;

&lt;p&gt;In order to use the &lt;code&gt;AuthenticatedSocketAdapter&lt;/code&gt;, the custom WebSocket adapter that we created, we should add the following code to &lt;strong&gt;main.ts&lt;/strong&gt; file as below.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&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;AppModule&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;useWebSocketAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuthenticatedSocketAdapter&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="c1"&gt;// Add our custom socket adapter.&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Don’t forget to set the &lt;code&gt;SocketsGateway&lt;/code&gt; as a provider in the &lt;strong&gt;app.module.ts&lt;/strong&gt;&lt;/p&gt;

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

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppController&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SocketsGateway&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  6. Create rooms.ts helper file
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;rooms.ts&lt;/strong&gt; is there to keep track of the connected users. This is the place where I will write the application logic related to our server-side timer. The methods in this file would send messages to clients as required. &lt;/p&gt;



&lt;p&gt;First, create a global variable &lt;code&gt;userTimers&lt;/code&gt;, to keep track of the connected users and their timers. Here, the keys of the object are the &lt;code&gt;userId + deviceId&lt;/code&gt; combinations. The values are the &lt;code&gt;timer&lt;/code&gt;s of each user.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;userTimers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// To keep track of timers for each user.&lt;/span&gt;


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

&lt;/div&gt;

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

&lt;p&gt;Here is how the &lt;code&gt;userTimers&lt;/code&gt; variable would look like.&lt;/p&gt;

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

&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user001device001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* data of timer1 */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user002device002&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* data of timer2 */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user003device003&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* data of timer3 */&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;But for more scalable applications, an approach which uses &lt;strong&gt;Redis&lt;/strong&gt; and &lt;strong&gt;microservices&lt;/strong&gt; and thereby making our backend application stateless would be advisable - then unexpected shutdowns of the backend server would not cause loss of user timers. This is because we can use Redis as a high speed key-value store.&lt;/p&gt;




&lt;p&gt;Next, create a method &lt;code&gt;getUserDeviceRoom(){}&lt;/code&gt;, to keep track of a single device of a user, based on &lt;code&gt;userId&lt;/code&gt; and the &lt;code&gt;deviceId&lt;/code&gt;. It returns the &lt;strong&gt;roomName&lt;/strong&gt; as a string for that user device.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserDeviceRoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`user:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-device:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This way, we can use this returned &lt;strong&gt;roomName&lt;/strong&gt; to create a conceptual room for that user device. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;handleConnection(){}&lt;/code&gt; method, the connected client device &lt;strong&gt;join&lt;/strong&gt; to this room. Then we can send a message to this specific device of a user, by sending that message to that room.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;handleDisconnect(){}&lt;/code&gt; method, the connected client device &lt;strong&gt;leave&lt;/strong&gt; from this room. We can no longer send messages to that user device until it establishes a socket connection again.&lt;/p&gt;




&lt;p&gt;Next, create the method &lt;code&gt;sendToUserDevice(){}&lt;/code&gt; to send the actual message using WebSockets.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendToUserDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getUserDeviceRoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deviceId&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Actually send the message to the user device via WebSocket channel.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;




&lt;p&gt;Next, we have to implement the methods to start the timer and stop the timer. I am using the &lt;code&gt;setInterval()&lt;/code&gt; method of JavaScript to create a counter, and that &lt;code&gt;counter&lt;/code&gt; value is sent to the user device in each &lt;strong&gt;tick&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the same time, when a timer is created, I add it to a variable &lt;code&gt;timer&lt;/code&gt; and it is stored in the variable &lt;code&gt;userTimers&lt;/code&gt; that I mentioned in the beginning.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startTimerForUserDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;var&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Set initial counter value to timer duration `dur` (in seconds).&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;timer&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="nf"&gt;function &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="s2"&gt;`counting &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;sendToUserDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TimerEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Send tick message to user device.&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Stop timer for this user.&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="s2"&gt;`user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has a timeout`&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;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;userTimers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Store timer for this user device.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We store the &lt;code&gt;timer&lt;/code&gt;s in the &lt;code&gt;userTimers&lt;/code&gt; variable because we need a reference to the timer of each user, to stop it later if the client requests (using the Flutter app) to stop the server-side timer for that device. The relevant code is shown below.&lt;/p&gt;

&lt;p&gt;After clearing the interval for that timer, we also delete it from the &lt;code&gt;userTimers&lt;/code&gt; object, to release the memory allocated for it.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stopTimerForUserDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&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;userTimers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// Stop the timer for this user device.&lt;/span&gt;

  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;userTimers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Delete the timer for this user device from the `userTimers` object.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Don’t forget to import this file inside the &lt;strong&gt;sockets.gateway.ts&lt;/strong&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Create events.ts helper file
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;events.ts&lt;/strong&gt; file just contains an &lt;code&gt;enum&lt;/code&gt; to easily keep track of the &lt;strong&gt;message events&lt;/strong&gt; I mentioned earlier.&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;TimerEvents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;tick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timerStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timerStart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timerStop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timerStop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The reason to use enums is because this way we can use the variable name of the enum instead of typing the exact event name as a string everywhere that we need it - this reduces errors due to typing mistakes.&lt;/p&gt;

&lt;p&gt;Don’t forget to import this file inside the &lt;strong&gt;sockets.gateway.ts&lt;/strong&gt; file.&lt;/p&gt;

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

&lt;p&gt;We wrote code in the following files in our NestJS server application to enable WebSocket communication. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sockets.gateway.ts&lt;/li&gt;
&lt;li&gt;authenticated-sockets.adapter.ts&lt;/li&gt;
&lt;li&gt;rooms.ts&lt;/li&gt;
&lt;li&gt;events.ts&lt;/li&gt;
&lt;li&gt;app.module.ts&lt;/li&gt;
&lt;li&gt;main.ts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although it is recommended to restructure these files according to a proper standard in a production application, this would be more than enough for us for now. &lt;/p&gt;

&lt;p&gt;When you are done with this section, please move onto the next section of this 3-part series to see how to setup the flutter front end to connect to this backend WebSocket implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support me!
&lt;/h2&gt;

&lt;p&gt;Do you think I deserve a cup of coffee for this article? 😃&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/rukshanjs" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Video
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=EFEtg-vL92k" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.youtube.com%2Fvi%2FEFEtg-vL92k%2F0.jpg" alt="IMAGE ALT TEXT HERE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;You can find the full source code of the project at my GitHub, &lt;a href="https://github.com/RukshanJS/websockets-nestjs-flutter" rel="noopener noreferrer"&gt;https://github.com/RukshanJS/websockets-nestjs-flutter&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Official NestJS docs on sockets -&lt;a href="https://docs.nestjs.com/websockets/gateways" rel="noopener noreferrer"&gt;https://docs.nestjs.com/websockets/gateways&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why Socket.IO is needed? - &lt;a href="https://stackoverflow.com/a/32811489/8995555" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/32811489/8995555&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why two packages are needed for WebSocket implementation in NestJS? - &lt;a href="https://stackoverflow.com/a/73339808/8995555" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/73339808/8995555&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>websockets</category>
      <category>flutter</category>
      <category>nestjs</category>
      <category>realtime</category>
    </item>
    <item>
      <title>Part 1/3 - How to create a server-side timer using WebSockets (with Socket.IO), NestJS and Flutter</title>
      <dc:creator>Rukshan J. Senanayaka</dc:creator>
      <pubDate>Mon, 15 Aug 2022 15:30:00 +0000</pubDate>
      <link>https://forem.com/rukshanjs/part-13-how-to-create-a-server-side-timer-using-websockets-with-socketio-nestjs-and-flutter-3821</link>
      <guid>https://forem.com/rukshanjs/part-13-how-to-create-a-server-side-timer-using-websockets-with-socketio-nestjs-and-flutter-3821</guid>
      <description>&lt;p&gt;If you have an interest in WebSockets technology, chances are that you have seen several tutorials online that use it to create applications such as realtime chat apps. But is that all that is possible with this WebSockets technology 🤔? - Of course not! We can do so much more with them. &lt;/p&gt;

&lt;p&gt;In this 3-part tutorial series, we will learn how to create a server-side timer using NestJS and Flutter by application of WebSockets (with Socket.IO). You can use such an implementation to create a proper timer for a quiz app etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A decent knowledge on NestJS, Flutter, JavaScript/TypeScript.&lt;/li&gt;
&lt;li&gt;Flutter 2.5.2 (with null safety), NestJS 8.0.0, Node 16.15.0, Android Emulator API S (This series of articles is successfully tested with these - may or may not work with other versions)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final outcome of this 3-part article series
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This part (part 1/3) contains the basic theory and some other good-to-know info related to WebSocket communication.&lt;/p&gt;

&lt;p&gt;In the world of connectivity, users need to send and receive data to and from various locations around the world. REST APIs are used widely to achieve this purpose. But in the traditional client-server communication using REST API and HTTP protocol, the server only responds to a request firstly made by the client - not initiate a data transfer by itself. &lt;/p&gt;

&lt;p&gt;Therefore, to get new data, a client has to request new data each time it needs that updated data. But how does a client know whether it's the right time to request new data or to wait for some time until the data is updated in the server?&lt;/p&gt;

&lt;p&gt;A few methods are available to achieve this,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refresh the client to send a new HTTP request every time&lt;/li&gt;
&lt;li&gt;Polling&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of these, refreshing the client repeatedly is not good user experience. It is also not sometimes practical.&lt;/p&gt;

&lt;p&gt;Polling is expensive in terms of computational power and resources required.&lt;/p&gt;

&lt;p&gt;WebSockets is a high speed technology that can be used for realtime client-server communication.&lt;/p&gt;

&lt;h2&gt;
  
  
  The socket confusion
&lt;/h2&gt;

&lt;p&gt;First off, let's clarify a couple of things.&lt;/p&gt;

&lt;p&gt;There are a couple of jargon words used in socket communication (mostly interchangeably) in general, but it would be nice to have a clear idea about them. I'll just give a brief of some such words below and you can refer the references for more information on them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket&lt;/strong&gt; - is a protocol that depends on &lt;strong&gt;TCP&lt;/strong&gt; protocol (like &lt;strong&gt;HTTP&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSockets&lt;/strong&gt; - is the API specification of WebSocket protocol (supports two way communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socket.IO&lt;/strong&gt; - is a library (built on top of WebSocket protocol)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socket&lt;/strong&gt; - is an endpoint (in a socket communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook&lt;/strong&gt; - is an HTTP-based callback function which allows one way, server to server communication  (&lt;strong&gt;not&lt;/strong&gt; related to WebSockets and &lt;strong&gt;not&lt;/strong&gt; concerned in this tutorial series)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although &lt;strong&gt;Socket&lt;/strong&gt;s can be divided as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP/IP sockets&lt;/li&gt;
&lt;li&gt;Unix domain sockets,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we are only interested in TCP/IP sockets for this tutorial series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps in establishing a WebSocket connection
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Client sends an HTTP request to open a WebSocket connection (the &lt;strong&gt;HTTP&lt;/strong&gt; connection will be upgraded to a &lt;strong&gt;WebSocket&lt;/strong&gt; connection)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server responds to the client’s request approving it with a &lt;strong&gt;101 Switching Protocols&lt;/strong&gt; response, and thereby completing the handshake and establishing a WebSocket connection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This TCP/IP connection is kept open (by a ping-pong mechanism) allowing real time communication between the client and server until either the client or the server chooses to close the connection. This is a full-duplex communication, meaning both the client and server can send messages to each other at the same time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After either side terminates the connection, the TCP resources would be unallocated.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In traditional client-server communication, a client sends a request to a server to retrieve some kind of a resource (a text, an image or an audio etc.) and waits for a response from the server. But in WebSocket communication, the server can send the resource when required - without a direct request from a client. &lt;/p&gt;

&lt;p&gt;With the successful establishing of a handshake, a communication channel is created between client and server and then whenever the server wants to send something, it just can send it to the particular client without needing a REST API request from the client itself.&lt;/p&gt;

&lt;p&gt;Either endpoint of this communication channel is called a &lt;strong&gt;Socket&lt;/strong&gt;. Therefore, a socket has an IP address and a port number.&lt;/p&gt;

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

&lt;p&gt;In this 3-part article series, we would be implementing a server-side timer using NestJS and Flutter, and we can start and stop it using our Flutter application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;I'm also learning this stuff and there can be better ways to do the things that I have done in this tutorial series. Please feel free to give your feedback in the comments. If I have made a mistake, I'm more than grateful if you can bring them up in the comments section in each article 🤓&lt;/p&gt;

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

&lt;p&gt;Sockets are a great way to implement realtime functionalities in applications. For example, WebSockets can be used to implement a server-side timer for a Flutter app. &lt;/p&gt;

&lt;p&gt;This way, the clients have no way to tamper with or manipulate the timer on the frontend, and therefore this can be used, for example in quiz apps to maintain a server-side timer for each question with high integrity.&lt;/p&gt;

&lt;p&gt;Now you can go to the part 2 of this tutorial series, to see how to implement the backend server application required for WebSocket communication, in NestJS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=fV0lRcfb1zI" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.youtube.com%2Fvi%2FfV0lRcfb1zI%2F0.jpg" alt="IMAGE ALT TEXT HERE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;General concept of a socket - &lt;a href="https://medium.com/swlh/understanding-socket-connections-in-computer-networking-bac304812b5c" rel="noopener noreferrer"&gt;https://medium.com/swlh/understanding-socket-connections-in-computer-networking-bac304812b5c&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSocket Protocol - &lt;a href="https://datatracker.ietf.org/doc/id/draft-ietf-hybi-thewebsocketprotocol-09.html" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/id/draft-ietf-hybi-thewebsocketprotocol-09.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSocket API (WebSockets) - &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Socket.IO can be more than just a wrapper for WebSockets - &lt;a href="https://en.wikipedia.org/wiki/Socket.IO" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Socket.IO&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets v Socket.IO - &lt;a href="https://stackoverflow.com/a/62848079/8995555" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/62848079/8995555&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSocket v Socket - &lt;a href="https://stackoverflow.com/a/67826460/8995555" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/67826460/8995555&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets v Webhooks - &lt;a href="https://stackoverflow.com/a/24747947/8995555" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/24747947/8995555&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unix Socket v TCP/IP Socket (Just for additional knowledge) - &lt;a href="https://www.baeldung.com/linux/unix-vs-tcp-ip-sockets" rel="noopener noreferrer"&gt;https://www.baeldung.com/linux/unix-vs-tcp-ip-sockets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is a Webhook - &lt;a href="https://www.redhat.com/en/topics/automation/what-is-a-webhook" rel="noopener noreferrer"&gt;https://www.redhat.com/en/topics/automation/what-is-a-webhook&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP to WS upgrade request - &lt;a href="https://stackoverflow.com/a/26401940/8995555" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/26401940/8995555&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>websockets</category>
      <category>flutter</category>
      <category>nestjs</category>
      <category>realtime</category>
    </item>
    <item>
      <title>Introduction to Next.js - a personal opinion</title>
      <dc:creator>Rukshan J. Senanayaka</dc:creator>
      <pubDate>Wed, 27 Jul 2022 18:23:00 +0000</pubDate>
      <link>https://forem.com/rukshanjs/introduction-to-nextjs-a-personal-opinion-emn</link>
      <guid>https://forem.com/rukshanjs/introduction-to-nextjs-a-personal-opinion-emn</guid>
      <description>&lt;p&gt;Hi! First off, thanks for having a look at my very first article on dev.to 🥳! In this short article I’m going to give an introduction to Next.js while giving you my personal opinion on it as well. Your comments and ideas are also welcome 😎!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s in this article
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is Next.js&lt;/li&gt;
&lt;li&gt;Some personal-favorite features of Next.js&lt;/li&gt;
&lt;li&gt;Choosing Next.js v React&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What’s not in this article
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to start a Next.js project &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to create only a very simple informational website, learning HTML and CSS is just enough. But if you want to add some functionality to that site, you may want to use JavaScript as well. Any website - no matter how complex, almost always can be simplified into HTML, CSS and JS code. &lt;/p&gt;

&lt;p&gt;But what if you want to create a somewhat complex website or a web app instead of a simple one? That’s where web development libraries such as React.js and frameworks such as Next.js, Vue.js and Angular come in handy.&lt;/p&gt;

&lt;p&gt;Next.js can be thought of as a full-stack web development framework, because it allows the developer to write code for frontend and backend in the same application. The frontend code is of course written in React using either JavaScript or TypeScript. (Although my personal favorite is with TypeScript - I’ll tell you why in a future article)&lt;/p&gt;

&lt;p&gt;One major question I get asked is, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Do I need to learn React to start learning Next.js?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My answer to this question is, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Don’t think of Next.js as a completely isolated thing from React, just think of it as an additional layer of help provided by the developers of Next.js to React developers because under the hood, Next.js is React!”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check the following code snippets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hello World in React,&lt;/strong&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="c1"&gt;//src/App.jsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&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="p"&gt;(&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;World&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Hello World in Next.js,&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//pages/_app.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styles/globals.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppProps&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;next/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&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;pageProps&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AppProps&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;World&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See, very similar. That’s because what Next.js does, is it takes care of some of the mundane yet common tasks needed to be done during the development of a React app. &lt;/p&gt;

&lt;p&gt;As an example, developers who use React know the pain it takes to handle routes. If a route name is changed for example from &lt;code&gt;auth/login&lt;/code&gt; to &lt;code&gt;/login&lt;/code&gt; (among the many requests of real-world clients 😆) then a React developer has to manually change the route in the code. But, a Next.js developer only has to change the folder structure inside the &lt;code&gt;pages&lt;/code&gt; folder. (This folder is a Next.js reserved folder for routing, by the way).&lt;/p&gt;

&lt;h2&gt;
  
  
  Features i like about Next.js
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Page-based routing allows easy management of routing within the web app and it supports dynamic routes which allow easy handling of query params etc. If for example you have a &lt;code&gt;projects/[projectId]&lt;/code&gt; sort of route, getting that &lt;code&gt;projectId&lt;/code&gt; to display contents of a single project is very easy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hot code reloading which allows to see changes updated when saving a code file without server restart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy deployment on various platforms like Vercel (offered by creators of Next.js) and Netlify.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cool documentation which is very beginner-friendly. (But I was not able to get Server-side-rendering working yet by reading that).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I don’t like about Next.js
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Some third party packages which import global css in them will give an error. For example, &lt;a href="https://github.com/uiwjs/react-md-editor/issues/52#issue-724437226"&gt;https://github.com/uiwjs/react-md-editor/issues/52#issue-724437226&lt;/a&gt;. There are workarounds, but it’s an inconvenience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sometimes the dev version of the web app is too slow in the browser. No problem once built the production release.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Ok then bye bye React! Hello Next.js? Not so fast 😄! While Next.js is a very good framework for mid to complex projects, for very simple projects, it is wise to think of using plain React. (If it is even simpler than that, just use HTML, CSS and JS).&lt;/p&gt;

&lt;p&gt;One final note - If it is a fairly complex client project, I almost always ask the client if it is okay to use Next.js or if they want pure React. If they have no preference, I always select Next.js because why not!&lt;/p&gt;

&lt;p&gt;But don’t forget the good old HTML, CSS, JS because not all projects require React or Next.js. I mean who is to say you &lt;strong&gt;must&lt;/strong&gt; use React for everything, right? I mean if you want to have a look at the most overengineered, and insanely complicated way to write a hello world web app, take a look at the video by Chris Hawkes - &lt;a href="https://youtu.be/3nHQMAY_J1A"&gt;https://youtu.be/3nHQMAY_J1A&lt;/a&gt;, of course after giving me a like if you think I deserve one for this article 😃. &lt;/p&gt;

&lt;p&gt;I'm also learning this stuff so let me know if I missed something in the comments below. Thanks and have a great day 🥳!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;I was inspired by this article, please have a look at that also. - &lt;a href="https://dev.to/olenadrugalya/introduction-to-nextjs-3gi4"&gt;https://dev.to/olenadrugalya/introduction-to-nextjs-3gi4&lt;/a&gt; &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
