<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aslan Vatsaev</title>
    <description>The latest articles on DEV Community by Aslan Vatsaev (@avatsaev).</description>
    <link>https://dev.to/avatsaev</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%2F114437%2Fea5a02a8-4dbb-4e3a-9e1d-f82133156cd1.jpg</url>
      <title>DEV Community: Aslan Vatsaev</title>
      <link>https://dev.to/avatsaev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avatsaev"/>
    <language>en</language>
    <item>
      <title>Engineer's Guide to Local LLMs with LLaMA.cpp on Linux</title>
      <dc:creator>Aslan Vatsaev</dc:creator>
      <pubDate>Mon, 15 Sep 2025 00:27:47 +0000</pubDate>
      <link>https://dev.to/avatsaev/pro-developers-guide-to-local-llms-with-llamacpp-qwen-coder-qwencode-on-linux-15h</link>
      <guid>https://dev.to/avatsaev/pro-developers-guide-to-local-llms-with-llamacpp-qwen-coder-qwencode-on-linux-15h</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In this write up I will share my local AI setup on Ubuntu that I use for my personal projects as well as professional workflows (local chat, agentic workflows, coding agents, data analysis, synthetic dataset generation, etc).&lt;/p&gt;

&lt;p&gt;This setup is particularly useful when I want to generate large amounts of synthetic datasets locally, process large amounts of sensitive data with LLMs in a safe way, use local agents without sending my private data to third party LLM providers, or just use chat/RAGs in complete privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Compile &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;LlamaCPP&lt;/a&gt; on your machine, set it up in your PATH, keep it up to date (compiling from source allows to use the bleeding edge version of llamacpp so you can always get latest features as soon as they are merged into the master branch)&lt;/li&gt;
&lt;li&gt;Use llama-server to serve local models with very fast inference speeds&lt;/li&gt;
&lt;li&gt;Setup llama-swap to automate model swapping on the fly and use it as your OpenAI compatible API endpoint.&lt;/li&gt;
&lt;li&gt;Use systemd to setup llama-swap as a service that boots with your system and automatically restarts when the server config file changes&lt;/li&gt;
&lt;li&gt;Integrate local AI in Agent Mode into your terminal with &lt;a href="https://github.com/QwenLM/qwen-code" rel="noopener noreferrer"&gt;QwenCode/OpenCode&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExOGVyY3dzbnBhZ2xlYTFpbDh0eG9uZHd4bXV2bXY3cmwyZ2VnZXFpNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Ft6z697ffcxStEQrqEk%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="100%" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExOGVyY3dzbnBhZ2xlYTFpbDh0eG9uZHd4bXV2bXY3cmwyZ2VnZXFpNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Ft6z697ffcxStEQrqEk%2Fgiphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will also share what models I use for different types of workflows and different advanced configurations for each model (context expansion, parallel batch inference, multi modality, embedding, rereanking, and more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This will be a technical write up, and I will skip some things like installing and configuring basic build tools, &lt;a href="https://docs.nvidia.com/cuda/cuda-installation-guide-linux/" rel="noopener noreferrer"&gt;CUDA toolkit installation&lt;/a&gt;, git, etc, if I do miss some steps that where not obvious to setup, or something doesn't work from your end, please let me know in the comments, I will gladly help you out, and progressively update the article with new information and more details as more people complain about specific aspects of the setup process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Hardware
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RTX3090 Founders Edition 24GB VRAM&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The more VRAM you have the larger models you can load, but if you don't have the same GPU as long at it's an NVIDIA GPU it's fine, you can still load smaller models, just don't expect good agentic and tool usage results from smaller LLMs.&lt;/p&gt;

&lt;p&gt;RTX3090 can load a Q5 quantized 30B Qwen3 model entirely into VRAM, with up to 140t/s as inference speed and 24k tokens context window (or up 110K tokens with some flash attention magic)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Experience with working on a Linux Dev Box &lt;/li&gt;
&lt;li&gt;&lt;a href="https://ubuntu.com/download" rel="noopener noreferrer"&gt;Ubuntu 24 or 25&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://documentation.ubuntu.com/server/how-to/graphics/install-nvidia-drivers/" rel="noopener noreferrer"&gt;NVIDIA proprietary drivers installed (version 580 at the time of writing)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.nvidia.com/cuda-downloads?target_os=Linux&amp;amp;target_arch=x86_64&amp;amp;Distribution=Ubuntu&amp;amp;target_version=24.04&amp;amp;target_type=deb_local" rel="noopener noreferrer"&gt;CUDA toolking installed&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/" rel="noopener noreferrer"&gt;ROCm installed if you use an AMD GPU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Linux build tools + Git installed and configured&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Architecture
&lt;/h1&gt;

&lt;p&gt;Here is a rough overview of the architecture we will be setting up:&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%2Fxrb92o9x6j6s4fttip8m.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%2Fxrb92o9x6j6s4fttip8m.png" alt="Setup architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Installing and setting up Llamacpp
&lt;/h1&gt;

&lt;p&gt;LlamaCpp is a very fast and flexible inference engine, it will allow us to run LLMs in GGUF format locally.&lt;/p&gt;

&lt;p&gt;Clone the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:ggml-org/llama.cpp.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cd into the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;llama.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;compile llamacpp for CUDA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;-DGGML_CUDA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="nt"&gt;-DBUILD_SHARED_LIBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OFF &lt;span class="nt"&gt;-DLLAMA_CURL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="nt"&gt;-DGGML_CUDA_FA_ALL_QUANTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you have a different GPU, checkout the build guide &lt;a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="nt"&gt;-j&lt;/span&gt; &lt;span class="nt"&gt;--clean-first&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create llama.cpp binaries in &lt;code&gt;build/bin&lt;/code&gt; folder.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To update llamacpp to bleeding edge just pull the lastes changes from the master branch with &lt;code&gt;git pull origin master&lt;/code&gt; and run the same commands to recompile&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Add llamacpp to PATH
&lt;/h2&gt;

&lt;p&gt;Depending on your shell, add the following to you bashrc or zshrc config file so we can execute llamacpp binaries in the terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export LLAMACPP=[PATH TO CLONED LLAMACPP FOLDER]
export PATH=$LLAMACPP/build/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test that everything works correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-server &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgez6qtrcywpv2tu4oq2.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%2Flgez6qtrcywpv2tu4oq2.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test that inference is working correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-cli &lt;span class="nt"&gt;-hf&lt;/span&gt; ggml-org/gemma-3-1b-it-GGUF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Flggupoc26yf6evzp9miv.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%2Flggupoc26yf6evzp9miv.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! now that we can do inference, let move on to setting up llama swap&lt;/p&gt;

&lt;h1&gt;
  
  
  Installing and setting up llama swap
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/mostlygeek/llama-swap" rel="noopener noreferrer"&gt;llama-swap&lt;/a&gt;  is a light weight, proxy server that provides automatic model swapping to llama.cpp's server. It will automate the model loading and unloading through a special configuration file and provide us with an openai compatible REST API endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download and install
&lt;/h2&gt;

&lt;p&gt;Download the latest version from the releases page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mostlygeek/llama-swap/releases" rel="noopener noreferrer"&gt;https://github.com/mostlygeek/llama-swap/releases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(look for  &lt;code&gt;llama-swap_159_linux_amd64.tar.gz&lt;/code&gt; )&lt;/p&gt;

&lt;p&gt;Unzip the downloaded archive and put the &lt;code&gt;llama-swap&lt;/code&gt; executable somewhere in your home folder (eg: &lt;code&gt;~/llama-swap/bin/llama-swap&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Add it to your path :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH=$HOME/llama-swap/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;create an empty (for now) config file file in &lt;code&gt;~/llama-swap/config.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;test the executable &lt;/p&gt;

&lt;p&gt;&lt;code&gt;llama-swap --help&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl6iqatvejkec03eeaef.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%2Fkl6iqatvejkec03eeaef.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before setting up llama-swap configuration we first need to download a few GGUF models .&lt;/p&gt;

&lt;p&gt;To get started, let's download qwen3-4b and gemma gemma3-4b&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/ggml-org/Qwen3-4B-GGUF/blob/main/Qwen3-4B-Q8_0.gguf" rel="noopener noreferrer"&gt;https://huggingface.co/ggml-org/Qwen3-4B-GGUF/blob/main/Qwen3-4B-Q8_0.gguf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/ggml-org/gemma-3-4b-it-GGUF/blob/main/gemma-3-4b-it-Q8_0.gguf" rel="noopener noreferrer"&gt;https://huggingface.co/ggml-org/gemma-3-4b-it-GGUF/blob/main/gemma-3-4b-it-Q8_0.gguf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download and put the GGUF files in the following folder structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/models
├── google
│   └── Gemma3-4B
│       └── Qwen3-4B-Q8_0.gguf
└── qwen
    └── Qwen3-4B
        └── gemma-3-4b-it-Q8_0.gguf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have some ggufs, let's create a llama-swap config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Llama Swap config file
&lt;/h2&gt;

&lt;p&gt;Our llama swap config located in &lt;code&gt;~/llama-swap/config.yaml&lt;/code&gt; will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;macros&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen3-4b-macro"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;llama-server \&lt;/span&gt;
      &lt;span class="s"&gt;--port ${PORT} \&lt;/span&gt;
      &lt;span class="s"&gt;-ngl 80 \&lt;/span&gt;
      &lt;span class="s"&gt;--ctx-size 8000 \&lt;/span&gt;
      &lt;span class="s"&gt;--temp 0.7 \&lt;/span&gt;
      &lt;span class="s"&gt;--top-p 0.8 \&lt;/span&gt;
      &lt;span class="s"&gt;--top-k 20 \&lt;/span&gt;
      &lt;span class="s"&gt;--min-p 0 \&lt;/span&gt;
      &lt;span class="s"&gt;--repeat-penalty 1.05 \&lt;/span&gt;
      &lt;span class="s"&gt;--no-webui \&lt;/span&gt;
      &lt;span class="s"&gt;--timeout 300 \&lt;/span&gt;
      &lt;span class="s"&gt;--flash-attn on \&lt;/span&gt;
      &lt;span class="s"&gt;--jinja \&lt;/span&gt;
      &lt;span class="s"&gt;--alias Qwen3-4b \&lt;/span&gt;
      &lt;span class="s"&gt;-m /home/[YOUR HOME FOLDER]/models/qwen/Qwen3-4B/Qwen3-4B-Q8_0.gguf&lt;/span&gt;

  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gemma-3-4b-macro"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;llama-server \&lt;/span&gt;
      &lt;span class="s"&gt;--port ${PORT} \&lt;/span&gt;
      &lt;span class="s"&gt;-ngl 80 \&lt;/span&gt;
      &lt;span class="s"&gt;--top-p 0.95 \&lt;/span&gt;
      &lt;span class="s"&gt;--top-k 64 \&lt;/span&gt;
      &lt;span class="s"&gt;--no-webui \&lt;/span&gt;
      &lt;span class="s"&gt;--timeout 300 \&lt;/span&gt;
      &lt;span class="s"&gt;--flash-attn on \&lt;/span&gt;
      &lt;span class="s"&gt;-m /home/[YOUR HOME FOLDER]/models/google/Gemma3-4B/gemma-3-4b-it-Q8_0.gguf&lt;/span&gt;


&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen3-4b"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-- this is your model ID when calling the REST API&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;${Qwen3-4b-macro}&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gemma3-4b"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;${Gemma-3-4b-macro}&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start llama-swap
&lt;/h2&gt;

&lt;p&gt;Now we can start llama-swap with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
llama-swap &lt;span class="nt"&gt;--listen&lt;/span&gt; 0.0.0.0:8083 &lt;span class="nt"&gt;--config&lt;/span&gt; ~/llama-swap/config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can access llama-swap UI at: &lt;a href="http://localhost:8083" rel="noopener noreferrer"&gt;http://localhost:8083&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lmyl29flfvd1rbs3w2k.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%2F8lmyl29flfvd1rbs3w2k.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see all configured models, you can also load or unload them manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inference
&lt;/h2&gt;

&lt;p&gt;Let's do some inference via llama-swap REST API completions endpoint&lt;/p&gt;

&lt;p&gt;Calling Qwen3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8083/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ],
  "stream": false,
  "model": "Qwen3-4b"
}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling Gemma3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8083/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "messages": [
    {
      "role": "user",
      "content": "hello"
    }
  ],
  "stream": false,
  "model": "Gemma3-4b"
}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a response from the server that looks something like this, and llamaswap will automatically load the correct model into the memory with each request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello! How can I assist you today? 😊"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1757877832&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Qwen3-4b"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"system_fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b6471-261e6a20"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chatcmpl-JgolLnFcqEEYmMOu18y8dDgQCEx9PAVl"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache_n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.072&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_per_token_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.072&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_per_second"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;38.35532371893219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;80.737&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_per_token_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6.728083333333333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"predicted_per_second"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;148.63073931406916&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optional: Adding llamaswap as systemd service and setup auto restart when config file changes
&lt;/h2&gt;

&lt;p&gt;If you don't want to manually run the llama-swap command everytime you turn on your workstation or manually reload the llama-swap server when you change your config you can leverage systemd to automate that away, create the following files:&lt;/p&gt;

&lt;p&gt;Llamaswap service unit (if you are not using zsh adapt the &lt;code&gt;ExecStart&lt;/code&gt; accordingly)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.config/systemd/user/llama-swap.service&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Llama Swap Server
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/zsh -l -c "source ~/.zshrc &amp;amp;&amp;amp; llama-swap --listen 0.0.0.0:8083 --config ~/llama-swap/config.yaml"
WorkingDirectory=%h
StandardOutput=journal
StandardError=journal
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

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

&lt;/div&gt;



&lt;p&gt;Llamaswap restart service unit&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.config/systemd/user/llama-swap-restart.service&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Restart llama-swap service
After=llama-swap.service

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl --user restart llama-swap.service

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

&lt;/div&gt;



&lt;p&gt;Llamaswap path unit (will allow to monitor changes in the llama-swap config file and call the restart service whenever the changes are detected):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.config/systemd/user/llama-swap-config.path&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Monitor llamaswap config file for changes
After=multi-user.target

[Path]
# Monitor the specific file for modifications
PathModified=%h/llama-swap/config.yaml
Unit=llama-swap-restart.service

[Install]
WantedBy=default.target

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

&lt;/div&gt;



&lt;p&gt;Enable and start the units:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;llama-swap-restart.service llama-swap.service llama-swap-config.path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start llama-swap.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the service is running correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status llama-swap.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor llamaswap server logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; llama-swap.service &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever the llama swap config is updated, the llamawap proxy server will automatically restart, you can verify it by monitoring the logs and making an update to the config file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If were able to get this far, congrats, you can start downloading and configuring your own models and setting up your own config, you can draw some inspiration from my config available here: &lt;a href="https://gist.github.com/avatsaev/dc302228e6628b3099cbafab80ec8998" rel="noopener noreferrer"&gt;https://gist.github.com/avatsaev/dc302228e6628b3099cbafab80ec8998&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It contains some advanced configurations, like multi-modal inference, parallel inference on the same model, extending context length with flash attention and more&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Connecting QwenCode to local models
&lt;/h1&gt;

&lt;p&gt;Install &lt;a href="https://github.com/QwenLM/qwen-code" rel="noopener noreferrer"&gt;QwenCode&lt;/a&gt;&lt;br&gt;
And let's use it with &lt;a href="https://huggingface.co/Qwen/Qwen3-Coder-30B-A3B-Instruct" rel="noopener noreferrer"&gt;Qwen3 Coder 30B Instruct&lt;/a&gt; locally (I recommend having at least 24GB of VRAM for this one 😅)&lt;/p&gt;

&lt;p&gt;Here is my llama swap config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;macros&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen3-Coder-30B-A3B-Instruct"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;llama-server \&lt;/span&gt;
      &lt;span class="s"&gt;--api-key qwen \&lt;/span&gt;
      &lt;span class="s"&gt;--port ${PORT} \&lt;/span&gt;
      &lt;span class="s"&gt;-ngl 80 \&lt;/span&gt;
      &lt;span class="s"&gt;--ctx-size 110000 \&lt;/span&gt;
      &lt;span class="s"&gt;--temp 0.7 \&lt;/span&gt;
      &lt;span class="s"&gt;--top-p 0.8 \&lt;/span&gt;
      &lt;span class="s"&gt;--top-k 20 \&lt;/span&gt;
      &lt;span class="s"&gt;--min-p 0 \&lt;/span&gt;
      &lt;span class="s"&gt;--repeat-penalty 1.05 \&lt;/span&gt;
      &lt;span class="s"&gt;--cache-type-k q8_0 \&lt;/span&gt;
      &lt;span class="s"&gt;--cache-type-v q8_0 \&lt;/span&gt;
      &lt;span class="s"&gt;--no-webui \&lt;/span&gt;
      &lt;span class="s"&gt;--timeout 300 \&lt;/span&gt;
      &lt;span class="s"&gt;--flash-attn on \&lt;/span&gt;
      &lt;span class="s"&gt;--alias Qwen3-coder-instruct \&lt;/span&gt;
      &lt;span class="s"&gt;--jinja \&lt;/span&gt;
      &lt;span class="s"&gt;-m /home/avatsaev/models/qwen/Qwen3-Coder-30B-A3B-Instruct-GGUF/Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL.gguf&lt;/span&gt;

&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen3-coder"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;${Qwen3-Coder-30B-A3B-Instruct}&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using &lt;a href="https://docs.unsloth.ai/models/unsloth-dynamic-2.0-ggufs" rel="noopener noreferrer"&gt;Unsloth's Dynamic quants&lt;/a&gt; at Q4 with flash attention and extending the context window to 100k tokens (with --cache-type-k and --cache-type-v flags), this is right at the edge of 24GBs of vram of my RTX3090. &lt;/p&gt;

&lt;p&gt;You can download qwen coder ggufs &lt;a href="https://huggingface.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF/tree/main" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a test scenario let's create a very simple react app in typescript&lt;/p&gt;

&lt;p&gt;Create an empty project folder &lt;code&gt;~/qwen-code-test&lt;/code&gt;&lt;br&gt;
Inside this folder create an &lt;code&gt;.env&lt;/code&gt; file with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"qwen"&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8083/v1"&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Qwen3-coder"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cd into the test directory and start qwen code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/qwen-code-test 
qwen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;make sure that the model is correctly set from your .env file:&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%2Fupy0ykoqp0j7ph3l5968.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%2Fupy0ykoqp0j7ph3l5968.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've installed &lt;a href="https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion" rel="noopener noreferrer"&gt;Qwen Code Companion extenstion&lt;/a&gt; in VS Code, and here are the results, a fully local coding agent running in VS Code 😁 &lt;/p&gt;

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

&lt;p&gt;Follow for more, like, and let me know how you will use this setup in your workflows in the comments.&lt;/p&gt;

&lt;p&gt;And if you have any questions, feel free to ask.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llamacpp</category>
      <category>tutorial</category>
      <category>llm</category>
    </item>
    <item>
      <title>Local Intelligence: How to set up a local GPT Chat for secure &amp; private document analysis workflow</title>
      <dc:creator>Aslan Vatsaev</dc:creator>
      <pubDate>Mon, 03 Jun 2024 09:21:32 +0000</pubDate>
      <link>https://dev.to/avatsaev/local-intelligence-how-to-set-up-a-local-gpt-chat-for-secure-private-document-analysis-workflow-1lnm</link>
      <guid>https://dev.to/avatsaev/local-intelligence-how-to-set-up-a-local-gpt-chat-for-secure-private-document-analysis-workflow-1lnm</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In this article, I'll walk you through the process of installing and configuring an Open Weights LLM (Large Language Model) locally such as Mistral or Llama3, equipped with a user-friendly interface for analysing your documents using RAG (Retrieval Augmented Generation). This setup allows you to analyse your documents without sharing your private and sensitive data with third-party AI providers such as OpenAI, Microsoft, Google, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can use pretty much any machine you want, but it's preferable to use a machine a dedicated GPU or Apple Silicon (M1,M2,M3, etc) for faster inference.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; must be preinstalled &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ollama
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftphep048m3fd7x5hneim.jpg" 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%2Ftphep048m3fd7x5hneim.jpg" alt=" " width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Ollama is a service that allows us to easily manage and run local open weights models such as &lt;strong&gt;Mistral&lt;/strong&gt;, Llama3 and more (see the &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;full list of available models&lt;/a&gt;). &lt;br&gt;
Ollama installation is pretty straight forward just download it from the &lt;a href="https://ollama.com/download" rel="noopener noreferrer"&gt;official website&lt;/a&gt; and run Ollama, no need to do anything else besides the installation and starting the Ollama service.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Ollama User Interface
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqxaeublobpbvesiebyj.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%2Fhqxaeublobpbvesiebyj.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Next step is installing the Ollama User Interface that will run on Docker, so Docker must be installed and running before installing the Ollama UI.&lt;/p&gt;

&lt;p&gt;To install the UI simply run the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:8080 &lt;span class="nt"&gt;--add-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host.docker.internal:host-gateway &lt;span class="nt"&gt;-v&lt;/span&gt; open-webui:/app/backend/data &lt;span class="nt"&gt;--name&lt;/span&gt; ollama-webui &lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WEBUI_AUTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;ghcr.io/open-webui/open-webui:main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install and start the Ollama UI webserver locally on address &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Download a local model
&lt;/h2&gt;

&lt;p&gt;Now that everything is up and running, we need to download a model.&lt;/p&gt;

&lt;p&gt;Good general purpose models as of today (May 2024) are &lt;strong&gt;Llama3&lt;/strong&gt; (from Meta) and &lt;strong&gt;Mistral&lt;/strong&gt;, in this article, I'll show how to install &lt;strong&gt;Mistral Instruct&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go to Ollama library: &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;https://ollama.com/library&lt;/a&gt; and type "mistral" in the search bar, then click on the first result:&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%2Fyg3d52x3bgzcteafyck8.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%2Fyg3d52x3bgzcteafyck8.png" alt=" " width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Pick the instruct variant in the dropdown menu:&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%2Fgf9fm10h3xvdn5vts63r.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%2Fgf9fm10h3xvdn5vts63r.png" alt=" " width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And copy the name and the tag of the model from the right side (don't copy the entire command just the &lt;code&gt;model_name:tag&lt;/code&gt; part):&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%2Fbl6nxgvij4rv1k98mjwq.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%2Fbl6nxgvij4rv1k98mjwq.png" alt=" " width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In the Ollama UI, click on the username, the bottom left corner, to display the pop over menu, click on "Settings":&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%2F4pqlnz8rbdjsy71e6xuz.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%2F4pqlnz8rbdjsy71e6xuz.png" alt=" " width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Then click on "Models" on the sidebar. This form below allows us to download &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;any model that Ollama supports&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the model tag &lt;code&gt;mistral:instruct&lt;/code&gt; in the text field and click download:&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%2Fe5h1rrv596f4kbyk77ri.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%2Fe5h1rrv596f4kbyk77ri.png" alt=" " width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;The model installation is the same for any other models in the Ollama Library&lt;/p&gt;

&lt;h2&gt;
  
  
  Chat with the model
&lt;/h2&gt;

&lt;p&gt;Once the model is downloaded, you can select it and set it as default:&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%2Fcqczg4enhhga3wwhgfkp.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%2Fcqczg4enhhga3wwhgfkp.png" alt=" " width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdpuoicvhsh1q8y35e0fl.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%2Fdpuoicvhsh1q8y35e0fl.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Let's see if everything works by sending a message to the model:&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%2F4ebu8hxyyfglfwn3ojt9.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%2F4ebu8hxyyfglfwn3ojt9.png" alt=" " width="800" height="894"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Great! The model is loaded and running without any issues 🎉🥳&lt;/p&gt;

&lt;p&gt;Now we can do some interesting things with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyse documents and data - RAG (Retrieval Augmented Generation)
&lt;/h2&gt;

&lt;p&gt;You can upload documents and ask questions related to these documents, not only that, you can also provide a publicly accessible Web URL and ask the model questions about the contents of the URL (an online documentation for example). All files you add to the chat will always remain on your machine and won't be sent to the cloud.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with a PDF document example
&lt;/h3&gt;

&lt;p&gt;Click the "+" icon in the chat and pick any PDF document you want:&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%2Fchgsis8800d15byh7cxj.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%2Fchgsis8800d15byh7cxj.png" alt=" " width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I've uploaded the "Attention All you need" paper as a PDF document, and asked a specific question related to this document:&lt;/p&gt;

&lt;p&gt;"What is the purpose of multi head attention mechanism?"&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%2Foq2bveityq8i7ej92ygx.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%2Foq2bveityq8i7ej92ygx.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Let's check if the RAG worked correctly by looking into the original PDF document:&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%2Fy7ai35art08qicky2dfu.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%2Fy7ai35art08qicky2dfu.png" alt=" " width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The RAG system was able to pinpoint the relevant part of the paper in order to answer the question 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask questions about the contents of a Web Page
&lt;/h3&gt;

&lt;p&gt;The URL of the web page must be publicly accessible, if you need to authenticate in order to view the page, the RAG won't work, so if you need to analyse a web page protected by auth, a workaround would be to first download it as PDF and upload it as a simple document.&lt;/p&gt;

&lt;p&gt;In the chat field type &lt;code&gt;#&lt;/code&gt; followed by a URL, for this example I'll use &lt;a href="https://doctolibpatient.zendesk.com/hc/fr/articles/360025314434-Ajouter-un-proche-sur-un-compte" rel="noopener noreferrer"&gt;Doctolib's FAQ&lt;/a&gt; about handling relatives in your Doctolib account:&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%2Fqfyzap63x6c2mpwuoykm.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%2Fqfyzap63x6c2mpwuoykm.png" alt=" " width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a4cs1saqvleesdt0h84.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%2F5a4cs1saqvleesdt0h84.png" alt=" " width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49ohosz2o4mwyjncc26t.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%2F49ohosz2o4mwyjncc26t.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Saving the documents to your Workspace
&lt;/h3&gt;

&lt;p&gt;You can also save your most often used documents in your workspace so you don't have to upload them every time, for that, click on "Workspace". go "Documents" tab, and upload your files here:&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%2Fovmo3nenqblxm6xkb3ja.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%2Fovmo3nenqblxm6xkb3ja.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Later when you want to work with your documents, just go to chat, and type &lt;code&gt;#&lt;/code&gt; in the message fields, you'll be presented with all documents from your work space, you can chose to work with one specific document or all of them in a single chat session:&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%2Fbj2jqlnp6pfu0gor6kdn.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%2Fbj2jqlnp6pfu0gor6kdn.png" alt=" " width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This is just scratching the surface, the Ollama UI can be configured to make the retrieval even more performant with some tricks. If you're interested in advanced configuration and usage of this workflow let me know in the comments.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>llm</category>
      <category>chat</category>
      <category>rag</category>
    </item>
    <item>
      <title>Simple state management in Angular with only Services and RxJS</title>
      <dc:creator>Aslan Vatsaev</dc:creator>
      <pubDate>Sun, 10 Feb 2019 20:55:07 +0000</pubDate>
      <link>https://dev.to/avatsaev/simple-state-management-in-angular-with-only-services-and-rxjs-41p8</link>
      <guid>https://dev.to/avatsaev/simple-state-management-in-angular-with-only-services-and-rxjs-41p8</guid>
      <description>&lt;p&gt;One of the most challenging things in software development is state management. Currently there are several state management libraries for Angular apps: NGRX, NGXS, Akita... All of them have different styles of managing state, the most popular being &lt;a href="https://ngrx.io/" rel="noopener noreferrer"&gt;NGRX&lt;/a&gt;, which pretty much follows the &lt;a href="https://facebook.github.io/flux/docs/in-depth-overview.html#content" rel="noopener noreferrer"&gt;FLUX/Redux&lt;/a&gt; principles from React world (basically using one way data flow and immutable data structures but with RxJS observable streams).&lt;/p&gt;

&lt;p&gt;But what if you don't want to learn, setup, use an entire state management library, and deal with all the boilerplate for a simple project, what if you want to &lt;strong&gt;manage state by only using tools you already know well as an Angular developer&lt;/strong&gt;, and still get the performance optimisations and coherency that state management libraries provide (On Push Change Detection, one way immutable data flow). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DISCLAIMER: This is not a post against state management libraries. We do use NGRX at work, and it really helps us to manage very complex states in very big and complex applications, but as I always say, NGRX complicates things for simple applications, and simplifies things for complex applications, keep that in mind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this write up, I'll show you a simple way of managing state by only using &lt;strong&gt;RxJS&lt;/strong&gt; and &lt;strong&gt;Dependency Injection&lt;/strong&gt;, all of our component tree will use OnPush change detection strategy.&lt;/p&gt;

&lt;p&gt;Imagine we have simple Todo app, and we want to manage its state, we already have our components setup and now we need a service to manage the state, let's create a simple Angular Service:&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;// todos-store.service.ts&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;provideIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&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;TodosStoreService&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;So what we need is, a way to provide a list of todos, a way to add todos, remove, filter, and complete them, we'll use getters/setters and RxJS's Behaviour Subject to do so:&lt;/p&gt;

&lt;p&gt;First we create ways to read and write in todos:&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;// todos-store.service.ts&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;provideIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&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;TodosStoreService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// - We set the initial state in BehaviorSubject's constructor&lt;/span&gt;
  &lt;span class="c1"&gt;// - Nobody outside the Store should have access to the BehaviorSubject &lt;/span&gt;
  &lt;span class="c1"&gt;//   because it has the write rights&lt;/span&gt;
  &lt;span class="c1"&gt;// - Writing to state should be handled by specialized Store methods (ex: addTodo, removeTodo, etc)&lt;/span&gt;
  &lt;span class="c1"&gt;// - Create one BehaviorSubject per store entity, for example if you have TodoGroups&lt;/span&gt;
  &lt;span class="c1"&gt;//   create a new BehaviorSubject for it, as well as the observable$, and getters/setters&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;_todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Expose the observable$ part of the _todos subject (read only stream)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;todos$&lt;/span&gt; &lt;span class="o"&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;_todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


  &lt;span class="c1"&gt;// the getter will return the last value emitted in _todos subject&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="c1"&gt;// assigning a value to this.todos will push it onto the observable &lt;/span&gt;
  &lt;span class="c1"&gt;// and down to all of its subsribers (ex: this.todos = [])&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="c1"&gt;// we assaign a new copy of todos by adding a new todo to it &lt;/span&gt;
    &lt;span class="c1"&gt;// with automatically assigned ID ( don't do this at home, use uuid() )&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;todos&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;removeTodo&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="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&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;todos&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;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


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

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

&lt;/div&gt;



&lt;p&gt;Now let's create a method that will allow us to set todo's completion status:&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;// todos-store.service.ts&lt;/span&gt;


&lt;span class="nf"&gt;setCompleted&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&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;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// we need to make a new copy of todos array, and the todo as well&lt;/span&gt;
    &lt;span class="c1"&gt;// remember, our state must always remain immutable&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise, on push change detection won't work, and won't update its view    &lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&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;todos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;isCompleted&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;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&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;And finally an observable source that will provide us with only completed todos:&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;// todos-store.service.ts&lt;/span&gt;

&lt;span class="c1"&gt;// we'll compose the todos$ observable with map operator to create a stream of only completed todos&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;completedTodos$&lt;/span&gt; &lt;span class="o"&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;todos$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todos&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;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCompleted&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now, our todos store looks something like this:&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;// todos-store.service.ts&lt;/span&gt;


&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&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;TodosStoreService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// - We set the initial state in BehaviorSubject's constructor&lt;/span&gt;
  &lt;span class="c1"&gt;// - Nobody outside the Store should have access to the BehaviorSubject &lt;/span&gt;
  &lt;span class="c1"&gt;//   because it has the write rights&lt;/span&gt;
  &lt;span class="c1"&gt;// - Writing to state should be handled by specialized Store methods (ex: addTodo, removeTodo, etc)&lt;/span&gt;
  &lt;span class="c1"&gt;// - Create one BehaviorSubject per store entity, for example if you have TodoGroups&lt;/span&gt;
  &lt;span class="c1"&gt;//   create a new BehaviorSubject for it, as well as the observable$, and getters/setters&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;_todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Expose the observable$ part of the _todos subject (read only stream)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;todos$&lt;/span&gt; &lt;span class="o"&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;_todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


  &lt;span class="c1"&gt;// we'll compose the todos$ observable with map operator to create a stream of only completed todos&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;completedTodos$&lt;/span&gt; &lt;span class="o"&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;todos$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todos&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;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCompleted&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// the getter will return the last value emitted in _todos subject&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="c1"&gt;// assigning a value to this.todos will push it onto the observable &lt;/span&gt;
  &lt;span class="c1"&gt;// and down to all of its subsribers (ex: this.todos = [])&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="c1"&gt;// we assaign a new copy of todos by adding a new todo to it &lt;/span&gt;
    &lt;span class="c1"&gt;// with automatically assigned ID ( don't do this at home, use uuid() )&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;todos&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;removeTodo&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="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&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;todos&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;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setCompleted&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&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;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// we need to make a new copy of todos array, and the todo as well&lt;/span&gt;
      &lt;span class="c1"&gt;// remember, our state must always remain immutable&lt;/span&gt;
      &lt;span class="c1"&gt;// otherwise, on push change detection won't work, and won't update its view&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&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;todos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;isCompleted&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;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now our smart components can access the store and manipulate it easily:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(PS: Instead of managing immutability by hand, I'd recommend using something  &lt;a href="https://immutable-js.github.io/immutable-js" rel="noopener noreferrer"&gt;ImmutableJS&lt;/a&gt;)&lt;/em&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;// app.component.ts&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;AppComponent&lt;/span&gt;  &lt;span class="p"&gt;{&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;public&lt;/span&gt; &lt;span class="nx"&gt;todosStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TodosStoreService&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app.component.html --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"all-todos"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;All todos&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;app-todo&lt;/span&gt; 
    &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let todo of todosStore.todos$ | async"&lt;/span&gt;
    &lt;span class="na"&gt;[todo]=&lt;/span&gt;&lt;span class="s"&gt;"todo"&lt;/span&gt;
    &lt;span class="na"&gt;(complete)=&lt;/span&gt;&lt;span class="s"&gt;"todosStore.setCompleted(todo.id, $event)"&lt;/span&gt;
    &lt;span class="na"&gt;(remove)=&lt;/span&gt;&lt;span class="s"&gt;"todosStore.removeTodo($event)"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/app-todo&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And here is the complete and final result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/edit/angular-rxjs-store?file=src%2Fapp%2Ftodos-store.service.ts" rel="noopener noreferrer"&gt;Full example on StackBlitz with a real REST API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/angular-rxjs-store?view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This is a scalable way of managing state too, you can easily inject other store services into each other by using Angular's powerful DI system, combine their observables with pipe operator to create more complex observables, and inject services like HttpClient to pull data from your server for example. No need for all the NGRX boilerplate or installing other State Management libraries. Keep it simple and light when you can.&lt;/p&gt;




&lt;p&gt;Follow me on Twitter for more interesting Angular related stuff: &lt;a href="https://twitter.com/avatsaev" rel="noopener noreferrer"&gt;https://twitter.com/avatsaev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>rxjs</category>
      <category>typescript</category>
      <category>ngrx</category>
    </item>
    <item>
      <title>Create efficient Angular Docker images with Multi Stage Builds</title>
      <dc:creator>Aslan Vatsaev</dc:creator>
      <pubDate>Tue, 13 Nov 2018 19:39:49 +0000</pubDate>
      <link>https://dev.to/avatsaev/create-efficient-angular-docker-images-with-multi-stage-builds-1f3n</link>
      <guid>https://dev.to/avatsaev/create-efficient-angular-docker-images-with-multi-stage-builds-1f3n</guid>
      <description>&lt;p&gt;In this write up we’ll see how to dockerize an Angular app in an efficient manner with Docker’s Multi-Stage Builds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At the time of writing this post, I'm using Angular v7&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS +8&lt;/li&gt;
&lt;li&gt;Angular CLI (&lt;code&gt;npm i -g @angular/cli@latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Docker +17.05&lt;/li&gt;
&lt;li&gt;Basic understanding of Docker and Angular CLI commands&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The plan
&lt;/h1&gt;

&lt;p&gt;To dockerize a basic Angular app built with Angular CLI, we need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm install the dependencies (dev dependencies included)&lt;/li&gt;
&lt;li&gt;ng build with --prod flag&lt;/li&gt;
&lt;li&gt;move the artifacts from &lt;code&gt;dist&lt;/code&gt; folder to a publicly accessible folder (via an an nginx server)&lt;/li&gt;
&lt;li&gt;Setup an nginx config file, and spin up the http server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://ultimatecourses.com/courses/angular/ref/azero79/" title="Ultimate Courses" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fultimatecourses.com%2Fassets%2Fimg%2Fbanners%2Fultimate-angular-github.svg" alt="Ultimate Courses" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We’ll do this in 2 stages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build stage: will depend on a Node alpine Docker image&lt;/li&gt;
&lt;li&gt;Setup stage: will depend on NGINX alpine Docker image and use the artifacts from the build stage, and the nginx config from our project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Initialize an empty Angular project
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ ng new myapp&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a default nginx config
&lt;/h2&gt;

&lt;p&gt;At the root of your Angular project, create nginx folder and create a file named default.conf with the following contents (./nginx/default.conf):&lt;br&gt;
&lt;/p&gt;

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

  listen 80;

  sendfile on;

  default_type application/octet-stream;


  gzip on;
  gzip_http_version 1.1;
  gzip_disable      "MSIE [1-6]\.";
  gzip_min_length   1100;
  gzip_vary         on;
  gzip_proxied      expired no-cache no-store private auth;
  gzip_types        text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
  gzip_comp_level   9;


  root /usr/share/nginx/html;


  location / {
    try_files $uri $uri/ /index.html =404;
  }

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the Docker file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;
&lt;span class="c"&gt;### STAGE 1: Build ###&lt;/span&gt;

&lt;span class="c"&gt;# We label our stage as ‘builder’&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:10-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;

&lt;span class="c"&gt;## Storing node modules on a separate layer will prevent unnecessary npm installs at each build&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; /ng-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; ./node_modules ./ng-app

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /ng-app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;## Build the angular app in production mode and store the artifacts in dist folder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run ng build &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--prod&lt;/span&gt; &lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dist


&lt;span class="c"&gt;### STAGE 2: Setup ###&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.14.1-alpine&lt;/span&gt;

&lt;span class="c"&gt;## Copy our default nginx config&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx/default.conf /etc/nginx/conf.d/&lt;/span&gt;

&lt;span class="c"&gt;## Remove default nginx website&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /usr/share/nginx/html/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;## From ‘builder’ stage copy over the artifacts in dist folder to default nginx public folder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /ng-app/dist /usr/share/nginx/html&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the image
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ docker build -t myapp .&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the container
&lt;/h2&gt;

&lt;p&gt;$ docker run -p 8080:80 myapp&lt;/p&gt;

&lt;p&gt;And done, your dockerized app will be accessible at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the size of the image is only ~15.8MB, which will be even less once pushed to a Docker repository.&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%2Fe0qsbzx3gzlgayakpsfb.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%2Fe0qsbzx3gzlgayakpsfb.png" width="800" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see a full example in this GitHub repository: &lt;a href="https://github.com/avatsaev/angular-contacts-app-example" rel="noopener noreferrer"&gt;https://github.com/avatsaev/angular-contacts-app-example&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>docker</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
