<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>vscode - Hard Wired</title>
	<atom:link href="https://www.hardwired.dev/tag/vscode/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.hardwired.dev</link>
	<description></description>
	<lastBuildDate>Wed, 15 Feb 2023 11:02:51 +0000</lastBuildDate>
	<language>cs</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://www.hardwired.dev/wp-content/uploads/2022/10/android-chrome-256x256-1-150x150.png</url>
	<title>vscode - Hard Wired</title>
	<link>https://www.hardwired.dev</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>ESP32 Bluetooth Server</title>
		<link>https://www.hardwired.dev/2023/02/01/esp32-bluetooth-server/</link>
		
		<dc:creator><![CDATA[John Doe]]></dc:creator>
		<pubDate>Wed, 01 Feb 2023 03:28:54 +0000</pubDate>
				<category><![CDATA[Arduino]]></category>
		<category><![CDATA[Hardware]]></category>
		<category><![CDATA[IOT]]></category>
		<category><![CDATA[Různé]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[ble]]></category>
		<category><![CDATA[bluetooth]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[esp32]]></category>
		<category><![CDATA[platformio]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[vscode]]></category>
		<guid isPermaLink="false">https://www.hardwired.dev/?p=938</guid>

					<description><![CDATA[<p>Dneska se podíváme jak zprovoznit Bluetooth na ESP32. Cílem je umožnit telefonu připojit se na naše Bluetooth zařízení a odpovídat &#62;&#62;&#62;</p>
<p>The post <a href="https://www.hardwired.dev/2023/02/01/esp32-bluetooth-server/">ESP32 Bluetooth Server</a> first appeared on <a href="https://www.hardwired.dev">Hard Wired</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Dneska se podíváme jak zprovoznit Bluetooth na ESP32. Cílem je umožnit telefonu připojit se na naše Bluetooth zařízení a odpovídat na specifické zprávy. Jako vývojové prostředí použijeme <a href="https://platformio.org/">PlatformIO</a>.</p>
<h1>ESP32</h1>
<p>Pro projekt použijeme <a href="https://www.aliexpress.com/item/32996458212.html">ESP32 Development Board</a> z Aliexpressu. V době psaní článku stojí zhruba 120CZK.</p>
<p><img decoding="async" src="https://www.hardwired.dev/wp-content/uploads/2023/02/esp32-aliexpress.jpg" alt="" /></p>
<h1>platformio.ini</h1>
<p>Konfigurační soubor pro projekt.</p>
<pre><code class="language-ini">[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200</code></pre>
<h1>main.cpp</h1>
<p>Jako základ kódu jsem použil výborný článek <a href="https://navody.dratek.cz/navody-k-produktum/esp32-a-bluetooth-low-energy-ble.html">ESP32 a Bluetooth Low Energy (BLE)</a> z <a href="https://navody.dratek.cz/">Drátek Návody</a> a provedl úpravy pro potřeby projektu. Pro zjednodušení nejprve celý kód projektu. Rozbor bude pokračovat.</p>
<pre><code class="language-c++">#include &lt;Arduino.h&gt;

// Bluetooth knihovny
#include &lt;BLEDevice.h&gt;
#include &lt;BLEServer.h&gt;
#include &lt;BLEUtils.h&gt;
#include &lt;BLE2902.h&gt;

// inicializace modulu z knihovny
BLECharacteristic *pCharacteristic;

// proměnná pro kontrolu připojených zařízení
bool deviceConnected = false;

// proměnná pro ukládání přijaté zprávy
std::string receivedMessage;

// proměnná pro zprávu pro odeslání
String out = &quot;&quot;;

// definice unikátních ID pro různé služby,
// pro vlastní UUID využijte generátor
// https://www.uuidgenerator.net/
#define SERVICE_UUID &quot;6E400001-B5A3-F393-E0A9-E50E24DCCA9E&quot;
#define CHARACTERISTIC_UUID_RX &quot;6E400002-B5A3-F393-E0A9-E50E24DCCA9E&quot;
#define CHARACTERISTIC_UUID_TX &quot;6E400003-B5A3-F393-E0A9-E50E24DCCA9E&quot;

int compareCommand(std::string message, String command)
{
    String converted = String(message.c_str());
    converted.trim();
    converted.toLowerCase();

    return converted == command;
}

void setOutputMessage(String message)
{
    out = message;
    out += &quot;\n&quot;;
}

void clearOutputMessage()
{
    out = &quot;&quot;;
}

void sendMessage()
{
    char message[out.length() + 1];
    out.toCharArray(message, out.length() + 1);

    // přepsání zprávy do BLE služby
    pCharacteristic-&gt;setValue(message);

    // odeslání zprávy skrze BLE do připojeného zařízení
    pCharacteristic-&gt;notify();
}

// třída pro kontrolu připojení
class MyServerCallbacks : public BLEServerCallbacks
{
    // při spojení zařízení nastav proměnnou na log1
    void onConnect(BLEServer *pServer)
    {
        deviceConnected = true;
    };
    // při odpojení zařízení nastav proměnnou na log0
    void onDisconnect(BLEServer *pServer)
    {
        deviceConnected = false;
        pServer-&gt;getAdvertising()-&gt;start();
    }
};

// třída pro příjem zprávy
class MyCallbacks : public BLECharacteristicCallbacks
{
    // při příjmu zprávy proveď následující
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        // načti přijatou zprávu do proměnné
        receivedMessage = pCharacteristic-&gt;getValue();

        // pokud není zpráva prázdná, vypiš její obsah
        // po znacích po sériové lince
        // a pokud přijde očekávaný příkaz pošli odpověď
        if (receivedMessage.length() &gt; 0)
        {
            Serial.print(&quot;Prijata zprava: &quot;);
            for (int i = 0; i &lt; receivedMessage.length(); i++)
            {
                Serial.print(receivedMessage[i]);
            }
            Serial.println();

            if (compareCommand(receivedMessage, &quot;ahoj&quot;))
            {
                setOutputMessage(&quot;No nazdar&quot;);
            }
        }
    }
};

void setup()
{
    // zahájení komunikace po sériové lince
    // rychlostí 115200 baud
    Serial.begin(115200);

    // inicializace Bluetooth s nastavením jména zařízení
    BLEDevice::init(&quot;ESP32 BLE&quot;);

    // vytvoření BLE serveru
    BLEServer *pServer = BLEDevice::createServer();
    pServer-&gt;setCallbacks(new MyServerCallbacks());

    // vytvoření BLE služby
    BLEService *pService = pServer-&gt;createService(SERVICE_UUID);

    // vytvoření BLE komunikačního kanálu pro odesílání (TX)
    pCharacteristic = pService-&gt;createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY);
    pCharacteristic-&gt;addDescriptor(new BLE2902());

    // vytvoření BLE komunikačního kanálu pro příjem (RX)
    BLECharacteristic *pCharacteristic = pService-&gt;createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE);
    pCharacteristic-&gt;setCallbacks(new MyCallbacks());

    // zahájení BLE služby
    pService-&gt;start();

    // zapnutí viditelnosti BLE
    pServer-&gt;getAdvertising()-&gt;start();
    Serial.println(&quot;BLE nastaveno, ceka na pripojeni..&quot;);
}

void loop()
{
    if (deviceConnected == true &amp;&amp; out.length() &gt; 0)
    {
        sendMessage();
        delay(100);
        clearOutputMessage();
    }

    // pauza před novým během smyčky
    delay(1000);
}</code></pre>
<h1>Rozbor kódu</h1>
<pre><code class="language-cpp">#include &lt;Arduino.h&gt;

// Bluetooth knihovny
#include &lt;BLEDevice.h&gt;
#include &lt;BLEServer.h&gt;
#include &lt;BLEUtils.h&gt;
#include &lt;BLE2902.h&gt;</code></pre>
<p>Knihovny potřebné pro obsluhu Bluetooth.</p>
<pre><code class="language-cpp">BLECharacteristic *pCharacteristic;</code></pre>
<p>Inicializace modulu knihovny pro práci s Bluetooth.</p>
<pre><code class="language-cpp">bool deviceConnected = false;</code></pre>
<p>Indikace, že je zařízení připojeno k našemu Bluetooth zařízení.</p>
<pre><code class="language-cpp">std::string receivedMessage;</code></pre>
<p>Proměnná pro uložení zprávy, kterou pošleme našemu Bluetooth zařízení.</p>
<pre><code class="language-cpp">String out = &quot;&quot;;</code></pre>
<p>Proměnná pro uložení odchozí zprávy.</p>
<pre><code class="language-cpp">#define SERVICE_UUID &quot;6E400001-B5A3-F393-E0A9-E50E24DCCA9E&quot;
#define CHARACTERISTIC_UUID_RX &quot;6E400002-B5A3-F393-E0A9-E50E24DCCA9E&quot;
#define CHARACTERISTIC_UUID_TX &quot;6E400003-B5A3-F393-E0A9-E50E24DCCA9E&quot;</code></pre>
<p>Bluetooth se skládá z jednotlivých služeb pro které je mít potřeba vygenerované unikátní identifikátory.</p>
<pre><code class="language-cpp">int compareCommand(std::string message, String command)
{
    String converted = String(message.c_str());
    converted.trim();
    converted.toLowerCase();

    return converted == command;
}</code></pre>
<p>Naše Bluetooth zařízení se chová jako server. Pokud mu pošlete zprávu/příkaz, který rozpozná, tak odpoví zpět. Příchozí zpráva se ukládá do proměnné typu <code>std::string</code>. Zprávu je potřeba očistit od bílých znaků, normalizovat (převést na malá písmena <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ) a porovnat s příkazem, který chceme provést. Tady byl lehce zádrhel, protože vhodné metody obsahuje zase <code>String</code> objekt. Céčko/C++ jsem provozoval naposledy na vysoké pro školní projekty a už tam jsem většinu věcí řešil v Pythonu. Jsem už prostě zmlsaný moderními programovacími jazyky. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Mé řešení asi nebude nejefektivnější, ale je lehce čitelné a funguje.</p>
<p>TLDR: <code>compareCommand</code> vezme příchozí zprávu, očistí od bordelu, porovná s námi zadaným příkazem a vrátí hodnotu porovnání.</p>
<pre><code class="language-cpp">void setOutputMessage(String message)
{
    out = message;
    out += &quot;\n&quot;;
}</code></pre>
<p>Pomocná funkce pro nastavení odchozí zprávy.</p>
<pre><code class="language-cpp">void clearOutputMessage()
{
    out = &quot;&quot;;
}</code></pre>
<p>Pomocná funkce pro vyčistění proměnné po odeslání zprávy.</p>
<pre><code class="language-cpp">void sendMessage()
{
    char message[out.length() + 1];
    out.toCharArray(message, out.length() + 1);

    pCharacteristic-&gt;setValue(message);

    pCharacteristic-&gt;notify();
}</code></pre>
<p>Funkce zapouzdřující odeslání zprávy. Převede zprávu na pole znaků, předá Bluetooth knihovně a pomocí metody <code>notify</code> odešle na připojené zařízení.</p>
<pre><code class="language-cpp">class MyServerCallbacks : public BLEServerCallbacks
{
    void onConnect(BLEServer *pServer)
    {
        zarizeniPripojeno = true;
    };

    void onDisconnect(BLEServer *pServer)
    {
        zarizeniPripojeno = false;
        pServer-&gt;getAdvertising()-&gt;start();
    }
};</code></pre>
<blockquote>
<p>Během testů se objevil problém s opětovným připojením. Když se telefon odpojil od Bluetooth serveru, nešlo se znovu připojit. Pomohl jen restart ESPčka. Dle různých zdrojů za to můžou různé verze knihoven. Pokud přidáme při odpojení zařízení <code>pServer-&gt;getAdvertising()-&gt;start();</code>, který začne propagovat náš Bluetooth server do prostoru, opětovné připojení funguje.</p>
</blockquote>
<p>Třída zapouzdřující <code>callbacky</code> pro náš Bluetooth server. Pokud se zařízení připojí/odpojí nastaví proměnnou <code>deviceConnected</code>.</p>
<pre><code class="language-cpp">class MyCallbacks : public BLECharacteristicCallbacks
{
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        receivedMessage = pCharacteristic-&gt;getValue();

        if (receivedMessage.length() &gt; 0)
        {
            Serial.print(&quot;Prijata zprava: &quot;);
            for (int i = 0; i &lt; receivedMessage.length(); i++)
            {
                Serial.print(receivedMessage[i]);
            }
            Serial.println();

            if (compareCommand(receivedMessage, &quot;ahoj&quot;))
            {
                setOutputMessage(&quot;No nazdar&quot;);
            }
        }
    }
};</code></pre>
<p>Třída zapouzdřující <code>callback</code> pro příjem zprávy. Při příchozí zprávě nenulové délky se jako první vypíše do sériové konzole. Následně proběhne porovnání jestli obsahuje, pro nás známý, příkaz. V našem případě <code>ahoj</code>. Pokud ano, tak odpoví <code>No nazdar</code>.</p>
<h2>setup()</h2>
<pre><code class="language-cpp">Serial.begin(115200);</code></pre>
<p>Nastavení rychlostí komunikace přes sériovou linku. Musí být stejné jako v <code>platformio.ini</code>.</p>
<pre><code class="language-cpp">BLEDevice::init(&quot;ESP32 BLE&quot;);</code></pre>
<p>Inicializace Bluetooth s nastavením jména, na které se bude dát z telefonu připojit.</p>
<pre><code class="language-cpp">BLEServer *pServer = BLEDevice::createServer();
pServer-&gt;setCallbacks(new MyServerCallbacks());</code></pre>
<p>Vytvoření Bluetooth serveru. Nastavíme <code>callbacky</code> pro detekci připojení/odpojení zařízení pomocí třídy nadefinované výše.</p>
<pre><code class="language-cpp">BLEService *pService = pServer-&gt;createService(SERVICE_UUID);</code></pre>
<p>Vytvoření Bluetooth služby.</p>
<pre><code class="language-cpp">pCharacteristic = pService-&gt;createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY);
pCharacteristic-&gt;addDescriptor(new BLE2902());</code></pre>
<p>Vytvoření komunikačního kanálu pro odesílání zpráv.</p>
<pre><code class="language-cpp">BLECharacteristic *pCharacteristic = pService-&gt;createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE);
pCharacteristic-&gt;setCallbacks(new MyCallbacks());</code></pre>
<p>Vytvoření kanálu pro příjem zpráv. Nastavujeme <code>callback</code> pro příjem nadefinovaný výše.</p>
<pre><code class="language-cpp">pService-&gt;start();</code></pre>
<p>Nastartování Bluetooth služby.</p>
<pre><code class="language-cpp">pServer-&gt;getAdvertising()-&gt;start();
Serial.println(&quot;BLE nastaveno, ceka na pripojeni..&quot;);</code></pre>
<p>Zviditelnění pro ostatní zařízení.</p>
<h2>loop()</h2>
<pre><code class="language-cpp">if (deviceConnected == true &amp;&amp; out.length() &gt; 0)
{
    sendMessage();
    delay(100);
    clearOutputMessage();
}

delay(1000);</code></pre>
<p><code>Loop</code> smyčka každou sekundu zkontroluje jestli je zařízení připojené k našemu Bluetooth serveru a jestli existuje zpráva co by šla odeslat. Pokud jsou obě podmínky splněny odešleme zprávu a vyčistíme data.</p>
<h2>TLDR</h2>
<p>Bluetooth server čeká na zp rávy. Pokud přijde zpráva, která obsahuje známy příkaz, nastaví se příslušné proměnné. Smyčka <code>loop</code> každou sekundu zkontroluje jestli jsou příslušné proměnné pro odchozí zprávu a pokud ano odešle zprávu na připojené zařízení. No raketová věda to teda není. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<h1>Serial Bluetooth Terminal</h1>
<p>Kód je zkompilovaný, nahraný na ESP32 a běží. Teď je potřeba začít s naším Bluetooth zařízením komunikovat. Pro Android jde použít výbornou aplikaci <a href="https://play.google.com/store/apps/details?id=de.kai_morich.serial_bluetooth_terminal&amp;hl=cs&amp;gl=US">Serial Bluetooth Terminal</a>. Umí to co potřebujeme. Připojit se k zařízení, posílat a obdržet zprávy.</p>
<p>Po instalaci se v obrazovce <code>Devices</code> přepněte do tabu <code>Bluetooth LE</code> a naskenujte zařízení. Objeví se naše ESPčko. Kliknutím na něj se připojíte.</p>
<p><img decoding="async" src="https://www.hardwired.dev/wp-content/uploads/2023/02/ble-terminal-01.jpg" alt="" /></p>
<p>Pokud připojení proběhne v pořádku zobrazí se následující.</p>
<p><img decoding="async" src="https://www.hardwired.dev/wp-content/uploads/2023/02/ble-terminal-02.jpg" alt="" /></p>
<p>Po odeslání zprávy <code>ahoj</code> dostaneme odpověď.</p>
<p><img decoding="async" src="https://www.hardwired.dev/wp-content/uploads/2023/02/ble-terminal-03.jpg" alt="" /></p>
<h1>Závěr</h1>
<p>Prezentovaný kód je takové minimum, bez hlubší znalosti Bluetooth a jeho vnitřního fungování, které ověřuje koncept ESP32 jako Bluetooth serveru odpovídajícího na příkazy.</p>
<p>Happy coding.</p>

<div class="twitter-share"><a href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.hardwired.dev%2F2023%2F02%2F01%2Fesp32-bluetooth-server%2F&#038;via=hessevalentino" class="twitter-share-button">Tweet</a></div><p>The post <a href="https://www.hardwired.dev/2023/02/01/esp32-bluetooth-server/">ESP32 Bluetooth Server</a> first appeared on <a href="https://www.hardwired.dev">Hard Wired</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>NodeJS develompent v Dockeru</title>
		<link>https://www.hardwired.dev/2022/09/17/nodejs-develompent-v-dockeru/</link>
		
		<dc:creator><![CDATA[John Doe]]></dc:creator>
		<pubDate>Sat, 17 Sep 2022 12:39:52 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Různé]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[docker-compose]]></category>
		<category><![CDATA[expressjs]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[nodejs]]></category>
		<category><![CDATA[nvm]]></category>
		<category><![CDATA[vagrant]]></category>
		<category><![CDATA[vscode]]></category>
		<guid isPermaLink="false">https://hessevalentino.cz/?p=378</guid>

					<description><![CDATA[<p>Pokud se motáte okolo webového vývoje (frontendy, api a pod...) a používáte JavaScript (respektive NodeJS), tak jste se určitě už &#62;&#62;&#62;</p>
<p>The post <a href="https://www.hardwired.dev/2022/09/17/nodejs-develompent-v-dockeru/">NodeJS develompent v Dockeru</a> first appeared on <a href="https://www.hardwired.dev">Hard Wired</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Pokud se motáte okolo webového vývoje (frontendy, api a pod...) a používáte <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript">JavaScript</a> (respektive <a href="https://nodejs.org/en/">NodeJS</a>), tak jste se určitě už setkali s následujícím problémem. Různé projekty jsou napsány pod různými verzemi programovacího jazyka a ne vždy je možné vše upgradovat na poslední verzi. </p>
<p><span id="more-378"></span></p>
<ul>
<li>Můžete používat knihovny, které ještě nejsou upraveny pro novou verzi.</li>
<li>Starší verze frameworku s poslední verzí jazyka nefunguje.</li>
<li>Zpětná nekompatibilita.</li>
<li>Prostě nejsou peníze nebo není čas zabývat se přechodem a přepisem aplikace na poslední verzi.</li>
</ul>
<p>Důvodů může být milión, ale klíčové je to, že pokud jste to vy co musí do těchto  projektů zasahovat, tak musíte provozovat více verzí jazyka.</p>
<p>Pokud se potýkáte s tímto problémem, tak nehledě na jakém operačním systému se nacházíte, budou scénáře podobné.</p>
<h2>Scénář 1 - Jedna verze v systému</h2>
<p>Tohle je pro popisovaný problém asi nejhorší situace. Máte v systému jednu verzi jazyka. Všechny projekty, na kterých pracujete, musejí běžet pod touhle verzí. Zpravidla to skončí tak, že verze jazyka bude taková, jakou potřebuje ten nejstarší (legacy) projekt. Může vás to zablokovat na tolik, že nebudete moci použít poslední verze frameworků nebo poslední funkcionalitu, protože nebudou tak starou verzi jazyka podporovat.</p>
<h2>Scénář 2 - NVM</h2>
<p><a href="https://github.com/nvm-sh/nvm">Node Version Manager</a> (NVM) je utilita, která vám umožní provozovat více verzí jazyka na jednom stroji a přepínat mezi nimi. Existuje jak pro Linux, MacOs, tak i pro Windows. Ve chvíli kdy se přepínáte mezi verzemi a používáte současně jednu, je to přímočaré a elegantní řešení. Ve chvíli, kdy potřebujete mít několik projektů puštěných naráz, pod různými verzemi jazyka, už samozřejmě vyžaduje určitou míru konfigurace. Ale zrovna k tomuto scénáři nemám moc co říct, protože jsem provozoval jen tu nejprimitivnější formu čistě přepínání mezi verzemi. Každopádně se jedná o funkční řešení.</p>
<h2>Scénář 3 - Vagrant</h2>
<p><a href="https://www.vagrantup.com/">Vagrant od HashiCorp</a> používá pro oddělení prostředí virtualizaci. Vytvoříte předpis, který nakonfiguruje virtuální stroj, ve kterém následně aplikaci vyvíjíte. Je možné nakonfigurovat celý ekosystém, aplikace, databáze, cache atd.  Teoreticky je to geniální věc. Nehledě na systém máte předpis, který spustíte na jakémkoliv systému a vytvoří vám stejné prostředí pro vývoj. V praxi to už tak veselé nebylo. Je pravda, že jsem Vagrant použil naposledy před několika lety. Problémy byly zpravidla nějaká specifika operačních systému, zvláště Windows. Nebo situace co se musely řešit &quot;hackem&quot;, protože ještě neexistovalo nativní řešení a samozřejmě hromada virtuálních mašin, když bylo prostředí pro vývoj rozsáhle. I tak jsem na tom byl nějakou dobu schopný vyvíjet a šlo to docela dobře. Čas pokročil a určitě byla hromada věcí dotažena. Určitě se jedná o funkční řešení.</p>
<h2>Scénář 4 - Docker</h2>
<p>Jak už název článku napovídá, budeme řešit <a href="https://www.docker.com/">Docker</a>. Docker používá kontejnery. Kontejner je softwarová jednotka, která zapouzdřuje kód i s jeho závislostmi. Na rozdíl od standardní virtualizace, nerozebíhá znovu celý operační systém, ale používá operační systém hostitele. Nejlíp to asi vysvětlí přímo citace ze stránek Dockeru.</p>
<blockquote>
<p>A container is a standard unit of software that packages up code and  all its dependencies so the application runs quickly and reliably from  one computing environment to another. A Docker container image is a  lightweight, standalone, executable package of software that includes  everything needed to run an application: code, runtime, system tools,  system libraries and settings.</p>
<p>Container images become containers at runtime and in the case of Docker containers – images become containers when they run on <a href="https://www.docker.com/products/container-runtime">Docker Engine</a>. Available for both Linux and Windows-based applications, containerized  software will always run the same, regardless of the infrastructure.  Containers isolate software from its environment and ensure that it  works uniformly despite differences for instance between development and staging.</p>
<p>from <a href="https://www.docker.com/resources/what-container/">https://www.docker.com/resources/what-container/</a></p>
</blockquote>
<p>V praxi to znamená, že máte v kontejneru souborový systém operačního systému dle libosti. Doinstalujete si potřebné balíčky pro běh vašeho projektu a tento kontejner můžete izolovaně pouštět tam, kde jsou kontejnery podporované. Speciálně Docker funguje pod Linuxem, MacOs i Windows.</p>
<pre><code>+------------++-----------+
|    APP1    ||   APP2    |
+------------++-----------+
+------------++-----------+
|  Bin/Libs  ||  Bin/Libs |
+------------++-----------+
+-------------------------+
|         Docker          |
+-------------------------+
+-------------------------+
|    Operační systém      |
+-------------------------+
+-------------------------+
|         Server          |
+-------------------------+</code></pre>
<p>&quot;Back in my days&quot;, když jsem si chtěl na serveru rozjet nějaké služby, buď jsem provozoval nějakou virtualizaci nebo jsem to všechno naplácal do jednoho operačního systému. Jedno ze zásadních úskalí bylo to, že různé služby potřebovaly různé balíky, verze knihoven a míru konfigurace. Pokud člověk nebyl dostatečně obezřetný mohl si &quot;roze***t&quot; celý systém a vše poslat do kytek. V lepším případě byla mašina v jakémsi meta ezo stavu, kdy něco jelo a něco ne.</p>
<p>S Dockerem hromada problémů opadla. Každá služba je izolovaný kontejner, který si své závislosti bere sebou. Pokud něco nefunguje, vadný kontejner se vymaže, ale vše ostatní jede dál. Drtivé množství běžných služeb (wiki, databáze, ...) mají už dnes nachystané a nakonfigurované obrazy. V uvozovkách je stačí jen spustit. Jako všechno si to sebou nese i negativa. Neaktualizované knihovny v kontejneru jsou bezpečnostní riziko. Nebo pokud si nevytváříte kontejner sami tak musíte důvěřovat autorovi, že tam nenasadil nějakou breberku. Ale vždy je něco za něco.</p>
<p>Jak už asi tušíte, z tohoto stručného popisu, použití pro výše popsaný problém bude jasné. Vytvoří se pro projekt Docker kontejner a tak budou všechny závislosti projektu izolovány od hlavního operačního systému. Takhle můžete mít pohodlně puštěné aplikace pod různou verzí NodeJS na jednom stroji. Je to v podstatě jako Vagrant, <a href="https://www.virtualbox.org/">VirtualBox</a> nebo obecně virtualizace. Akorát teda ne tak úplně. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>Nešpinit systém, na kterém vyvíjíte, závislostmi projektu je podle mě dlouhodobě udržitelná cesta.</p>
<p>Plus mají kontejnery dnes ještě jednu výhodu. <a href="https://aws.amazon.com/getting-started/hands-on/deploy-docker-containers/">Cloudové služby</a> vám zpravidla umožňují posílat kontejnery i do produkce.</p>
<h2>Čestná zmínka</h2>
<p>Výše uvedené scénáře nejsou určitě jediné, ale jsou to ty, se kterými jsem se setkal osobně.</p>
<p>Minimálně ještě existuje možnost vyvíjet na vzdáleném operačním systému přes <a href="https://cs.wikipedia.org/wiki/Secure_Shell">SSH</a>. <a href="https://code.visualstudio.com/">Visual Studio Code</a> tento <a href="https://code.visualstudio.com/docs/remote/ssh">vzdálený vývoj</a> umožňuje. Jako taky možnost, ale za mě teda trochu exotická. Ale když není zbytí, lepší než drátem do oka.</p>
<h2>NodeJS projekt v Dockeru</h2>
<blockquote>
<p>Toto není kompletní návod pro člověka, který se v životě neotřel o Docker nebo o NodeJS a vývoj API. Do takových podrobností článek nejde. Zabývá se jak rozjet projekt v Dockeru a předpokládá už určité znalosti. Pardon. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</blockquote>
<p>V první řadě budeme potřebovat <a href="http://wordpress.hardwired.dev/instalace-dockeru/">nainstalovat Docker</a>. Případně na desktopu (a hlavně na Windows) to bude <a href="https://docs.docker.com/desktop/">Docker Desktop</a>.</p>
<p>Pokusíme se rozjet vývoj <a href="https://zdrojak.cz/clanky/rest-architektura-pro-webove-api/">REST API</a> s použitím <a href="https://expressjs.com/">ExpressJs</a>.</p>
<p>Pro náš projekt budeme mít následující strukturu.</p>
<pre><code class="language-bash">nodejs-docker-workflow \
    .dockerignore
    .env
    Dockerfile
    index.js
    package.json
    package-lock.json</code></pre>
<p><code>pakcage.json</code> obsahuje konfiguraci projektu.</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;nodejs-docker-workflow&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node index.js&quot;,
    &quot;dev&quot;: &quot;nodemon -L index.js&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.18.1&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;nodemon&quot;: &quot;^2.0.20&quot;
  }
}</code></pre>
<p>V části závislostí budeme pro náš jednoduchý projekt potřebovat pouze balíček <a href="https://www.npmjs.com/package/express">express</a>. V rámci vývojových závislostí (devDependencies) budeme používat <a href="https://www.npmjs.com/package/nodemon">nodemon</a>. Ten nám umožní restart serveru, pokud se vyskytne změna v hlídaném adresáři.</p>
<p><code>index.js</code> obsahuje kód našeho REST API.</p>
<pre><code class="language-javascript">const express = require(&#039;express&#039;)

const app = express()

app.get(&#039;/&#039;, (req, res) =&gt; {
    res.send({
        data: {
            message: &#039;Hello world!&#039;,
            exclamation: &#039;!!!&#039;,
        },
    })
})

const port = process.env.PORT || 3000

app.listen(port, () =&gt; console.log(`listening on port ${port}`))</code></pre>
<p>Primitivní API vracející &quot;Hello world!&quot;.</p>
<p><code>Dockerfile</code> obsahuje sestavení našeho obrazu kontejneru.</p>
<pre><code class="language-dockerfile">FROM node:16
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . ./
ENV PORT 3000
EXPOSE $PORT
CMD [&quot;npm&quot;, &quot;run&quot;, &quot;dev&quot;]</code></pre>
<p><code>FROM node:16</code> říká, že použijeme základní image, na kterém je už nachystán NodeJS verze 16.</p>
<p><code>WORKDIR /app</code> nás v rámci kontextu přesune do složky <code>/app</code>.</p>
<p><code>COPY package*.json</code> . překopíruje do obrazu do <code>/app</code> soubor <code>package.json</code>.</p>
<p><code>RUN npm install</code> spustí instalaci závislostí na základe <code>package.json</code>.</p>
<p><code>COPY . ./</code> překopíruje soubory z aktuálního adresáře do obrazu do <code>/app</code>.</p>
<p><code>ENV PORT 3000</code> vytvoří proměnou prostředí.</p>
<p><code>CMD [&quot;npm&quot;, &quot;run&quot;, &quot;dev&quot;]</code> spustí skript definovaný v <code>package.json</code>, který spustí nodemon.</p>
<p><code>.dockerignore</code> obsahuje seznam souboru a složek co se nebudou kopírovat do obrazu.</p>
<pre><code class="language-bash">node_modules
Dockerfile
.dockerignore
.git
.gitignore
README.md
docker-compose*</code></pre>
<p><code>.env</code> je seznam proměnných prostředí, které chceme aplikovat na kontejner.</p>
<pre><code>PORT=4000</code></pre>
<p>Základní projekt máme nachystaný. A sestavíme na základě <code>Dockerfile</code> obraz pro náš kontejner.</p>
<pre><code class="language-bash">docker build -t node-app-image .</code></pre>
<p>Následně pak můžeme překontrolovat, že obraz skutečně existuje.</p>
<pre><code class="language-bash">docker image ls</code></pre>
<p>Obraz je nachystaný a můžeme spustit samotný kontejner.</p>
<pre><code class="language-bash"># WINDOWS %cd%
# POWERSHELL ${pwd}
# MAC, LINUX $(pwd)

# --env PORT=4000
# --env-file ./.env

docker run -p 3000:4000 -d --name node-app --env-file ./.env -v %cd%:/app -v /app/node_modules node-app-image</code></pre>
<p><code>-p 3000:4000</code> přesměruje port 3000 z hostitelského systému na port 4000 Docker kontejneru.</p>
<p><code>-d</code> spustí Docker kontejner na pozadí.</p>
<p><code>--name node-app</code> pojmenuje kontejner.</p>
<p><code>--env-file ./.env</code> nastaví proměnné systému na základě souboru <code>.env</code>.</p>
<p><code>-v %cd%:/app</code> zajistí zrcadlení aktuálního adresáře a jeho změny do <code>/app</code> v kontejneru.</p>
<p><code>-v /app/node_modules</code> vytvoří volume pro node moduly. Díky tomu se nestane to, že přes zrcadlený adresář pošleme příkaz k jejich smazání v kontejneru. Je to trochu &quot;ohák&quot;, ale funční.</p>
<p><code>node-app-image</code> je název obrazu na základě kterého vytváříme kontejner.</p>
<p>Pokud nedostaneme nějaké chybové hlášení, můžeme zkontrolovat, že vše jede.</p>
<p>Můžeme se podívat na aktuálně běžící kontejnery.</p>
<pre><code class="language-bash">docker ps</code></pre>
<p>A můžeme také zkontrolovat logy pro konkrétní kontejner.</p>
<pre><code class="language-bash">docker logs node-app</code></pre>
<p>Když do prohlížeče zadáme <code>localhost:3000</code> dostaneme následující.</p>
<pre><code class="language-json">{&quot;data&quot;:{&quot;message&quot;:&quot;Hello world!&quot;,&quot;exclamation&quot;:&quot;!!!&quot;}}</code></pre>
<p>Pokud v souboru <code>index.js</code> upravíme odpověď serveru, <code>nodemon</code> na to zareaguje a restartuje API s aktuální změnou.</p>
<p>Jestliže se potřebujeme &quot;přihlásit&quot; dovnitř kontejneru do příkazové řádky, použijeme <code>docker exec</code>.</p>
<pre><code class="language-bash">docker exec -it node-app bash</code></pre>
<p>V této chvíli máme plně funkční NodeJS projekt v Dockeru. Pokud přidáme nebo odebereme balíčky, musím znovu sestavit obraz pomocí <code>docker build -t node-app-image .</code> a Docker kontejner spustit.</p>
<p>Předtím je teda nutné kontejner zabít, to uděláme pomocí <code>docker rm</code>.</p>
<pre><code class="language-bash">docker rm node-app -fv</code></pre>
<p><code>-fv</code> odstraní i běžící kontejner (nemusíme zastavovat) a na něj navázané anonymní volumes (svazky).</p>
<p>Tohle je taková &quot;základní&quot; verze. Spouštění Docker kontejneru, s veškerou konfigurací v příkazové řádce může být po čase otrava. Jednodušším řešením je použití <a href="https://docs.docker.com/compose/gettingstarted/">Docker-Compose</a>.</p>
<p>V našem projektu vytvoříme soubor <code>docker-compose.yml</code>.</p>
<pre><code class="language-yaml">version: &quot;3&quot;
services:
  node-app:
    container_name: node-app
    build: .
    ports:
      - &quot;3000:4000&quot;
    volumes:
      - ./:/app
      - /app/node_modules
    env_file:
      - ./.env
#    environment:
#      - PORT=4000 </code></pre>
<p><code>version: &quot;3&quot;</code> je <a href="https://docs.docker.com/compose/compose-file/compose-versioning/">verze zápisu Docker Compose</a> souboru.</p>
<p><code>node-app:</code> uvozuje sekci našeho kontejneru.</p>
<p><code>container_name: node-app</code> pojmenuje náš kontejner. Pokud ho nepojmenujeme sami, Docker to udělá za nás.</p>
<p><code>build: .</code> sestaví obraz pomocí <code>Dockerfile</code>.</p>
<p><code>ports:</code> přesměrování portů.</p>
<p><code>volumes:</code> seznam volumes.</p>
<p><code>env_file:</code> nastavuje proměnné prostředí.</p>
<p>Docker Compose soubor jen kopíruje to, co jsme nastavovali pro <code>docker run</code>. Ale takhle nám zůstane nastavení pěkně a strukturovaně uloženo.</p>
<p>Předpis spustíme pomocí příkazu <code>docker-compose</code>.</p>
<pre><code class="language-bash">docker-compose up -d</code></pre>
<p>Pokud přidáme parametr <code>--build</code>, tak se při každém spuštění znovu sestaví obraz.</p>
<pre><code class="language-bash">docker-compose up -d --build</code></pre>
<p>Pomocí <code>docker-compose</code> provádíme i smazání.</p>
<pre><code class="language-bash">docker-compose down -v</code></pre>
<p><code>-v</code> jako v předchozím případě smaže i připojené anonymní volumes.</p>
<p>Tohle je už o něco lepší verze, ale nepočítá s tím, že bychom chtěli různá prostředí. Například <code>development</code> a <code>production</code>. Budeme muset upravit <code>Dockerfile</code>, <code>docker-compose.yml</code> a přidat <code>docker-compose.dev.yml</code> a <code>docker-compose.prod.yml</code>.</p>
<p>Do <code>Dockerfile</code> přidáme podmínku pro různá prostředí.</p>
<pre><code class="language-dockerfile">FROM node:16
WORKDIR /app
COPY package*.json .

ARG NODE_ENV
RUN if [ &quot;$NODE_ENV&quot; = &quot;development&quot; ]; \
        then npm install; \
        else npm install --only=production; \
        fi

COPY . ./
ENV PORT 3000
EXPOSE $PORT
CMD [&quot;node&quot;, &quot;index.js&quot;]</code></pre>
<p><code>ARG NODE_ENV</code> definujeme použití argumentu <code>NODE_ENV</code>.</p>
<p><code>RUN if [ &quot;$NODE_ENV&quot; = &quot;development&quot; ]; \</code> na základě aktuální typu prostředí provedeme klasický <code>npm install</code> nebo produkční.</p>
<p><code>CMD [&quot;node&quot;, &quot;index.js&quot;]</code> je příkaz do produkčního prostředí, protože nespouštíme <code>nodemon</code>. Každopádně ho stejně budeme definovat znovu v Docker Compose souborech.</p>
<p><code>docker-compose.yml</code> trošku zeštíhlíme a necháme v něm jen společná nastavení pro všechna prostředí.</p>
<pre><code class="language-yaml">version: &quot;3&quot;
services:
  node-app:
    container_name: node-app
    build: .
    ports:
      - &quot;3000:4000&quot;
    environment:
      - PORT=4000  </code></pre>
<p><code>docker-compose.dev.yml</code> nastavím pro naše <code>development</code> prostředí.</p>
<pre><code class="language-yaml">version: &quot;3&quot;
services:
  node-app:
    build:
      context: . 
      args:
        NODE_ENV: development
    volumes:
      - ./:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development  
    command: npm run dev</code></pre>
<p>Všimněte si, že jsme rozšířili definici <code>build</code> a přidali argument <code>NODE_ENV</code>, který se používá v <code>Dockerfile</code>. Nastavení <code>volumes</code> je stejné jako předtím a přepsali jsme <code>CMD</code> v <code>Dockerfile</code> na náš <code>development</code> příkaz.</p>
<p><code>docker-compose.prod.yml</code> bude jednodušší, protože pro produkci nepotřebujeme zrcadlit adresáře.</p>
<pre><code class="language-yaml">version: &quot;3&quot;
services:
  node-app:
    build:
      context: . 
      args:
        NODE_ENV: production
    environment:
      - NODE_ENV=production
    command: node index.js</code></pre>
<p>Principiálně je to to samé co <code>docker-compose.dev.yml</code>.</p>
<p>Pokud budeme chtít spustit <code>development</code> verzi, zavoláme.</p>
<pre><code class="language-bash">docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d</code></pre>
<p>Popřípadě, pokud chceme spustit i vytváření obrazu.</p>
<pre><code class="language-bash">docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build</code></pre>
<p>Odstranění kontejneru provedeme promocí.</p>
<pre><code class="language-shell">docker-compose -f docker-compose.yml -f docker-compose.dev.yml down -v</code></pre>
<p>Pro produkční build to bude analogické.</p>
<pre><code class="language-bash">docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.yml -f docker-compose.prod.yml down -v</code></pre>
<p>TADÁ a to je vše přátele. Aplikace funguje pořád stejně, ale už můžeme rozlišovat mezi <code>development</code> a <code>production</code> prostředtím.</p>
<hr />
<p><em>ad1)</em> Článek popisuje pouze absolutní základ, pravá síla Dockeru se projeví ve chvíli, kdy si nakonfigurujete kompletní ekosystém a budete ho schopni jednoduše replikovat.</p>
<p>ad2) Článek ukazuje situaci, kdy nebudete mít na svém stroji nainstalován NodeJS a nepustíte <code>npm install</code> na svém stroji. Tím si nevygenerujete <code>package-lock.json</code>. To může být problém při generování produkčních kontejnerů. <code>package-lock.json</code> obsahuje deterministický popis závislostí. Do produkce chcete jít s takovým stromem závislostí, který už máte odzkoušený. Pro doplnění citace z <a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json">docs.npm.com</a>.</p>
<blockquote>
<p><code>package-lock.json</code> is automatically generated for any operations where npm modifies either the <code>node_modules</code> tree, or <code>package.json</code>. It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates.</p>
<p>Describe a single representation of a dependency tree such that teammates, deployments, and continuous integration are guaranteed to install exactly the same dependencies.</p>
<p>from <a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json">https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json</a></p>
</blockquote>
<p>Proto je vhodnější postup na hostitelské systému používat <code>npm i</code> pro instalaci nových balíčku do projektu. Tím se bude generovat i <code>package-lock.json</code>. Toto nijak neomezuje používání rozličných verzí NodeJS v kontejnerech.</p>
<p>ad3) Docker se chová tak, že spustí příkaz a když doběhne, tak se vypne. V případě, že rozjíždíte NodeJs API, tak je to jedno. Tam se předpokládá, že pouštíte nějaký <code>nodemon</code> nebo něco co udrží API v běhu. Pokud ale chcete vyvíjet něco co neběží ve smyčce a z <code>Dockerfile</code> odeberete <code>CMD příkaz</code>, tak se <code>Docker container</code> spustí a úspěšně ukončí. Jde tím předejít přidáním parametru <code>tty</code>.<br />
Ten zajistí, že se kontejner hned neukončí.</p>
<p>Pro <code>docker-compose</code>:</p>
<pre><code class="language-yaml">mydocker:
    tty: true</code></pre>
<p>Pro <code>docker run</code>:</p>
<pre><code class="language-bash">docker run -d --tty ......</code></pre>

<div class="twitter-share"><a href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.hardwired.dev%2F2022%2F09%2F17%2Fnodejs-develompent-v-dockeru%2F&#038;via=hessevalentino" class="twitter-share-button">Tweet</a></div><p>The post <a href="https://www.hardwired.dev/2022/09/17/nodejs-develompent-v-dockeru/">NodeJS develompent v Dockeru</a> first appeared on <a href="https://www.hardwired.dev">Hard Wired</a>.</p>]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
