<?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: mark vachi</title>
    <description>The latest articles on DEV Community by mark vachi (@m4r14).</description>
    <link>https://dev.to/m4r14</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F535977%2F7ade663c-e917-42cb-9e0c-f31cce14213f.jpeg</url>
      <title>DEV Community: mark vachi</title>
      <link>https://dev.to/m4r14</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/m4r14"/>
    <language>en</language>
    <item>
      <title>Client API Key — Design (Lite) ออกแบบ API key service to service</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sat, 29 Aug 2026 04:48:17 +0000</pubDate>
      <link>https://dev.to/m4r14/client-api-key-design-lite-10hg</link>
      <guid>https://dev.to/m4r14/client-api-key-design-lite-10hg</guid>
      <description>&lt;p&gt;&lt;em&gt;ออกแบบ API key สำหรับการเรียกกัน service to service ให้เบาและยังถูกต้อง&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;วันหนึ่งจะมีอีกทีมมาขอเรียก service ของคุณ และคุณต้องรู้ให้ได้ว่าใครเป็นคนยิงเข้ามา&lt;/p&gt;

&lt;p&gt;ตัวเลือกที่คนส่วนใหญ่หยิบมีสองขั้ว — ยัดค่าสุ่ม uuid/hash ไว้ใน env var แล้วเทียบสตริง ซึ่งใช้ได้จริงจนถึงวันที่ key หลุด หรือลาก OAuth2 มาทั้งชุด ซึ่งซับซ้อนกว่าสิ่งที่กำลังจะปกป้องหลายเท่า โพสต์นี้คือทางกลาง&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;ตารางเดียว 6 คอลัมน์ กับฟังก์ชันสิบบรรทัด&lt;/p&gt;

&lt;p&gt;ของเล็กใครก็เขียนได้ ประเด็นอยู่ที่ว่าตัวนี้&lt;strong&gt;ไม่ทิ้งหนี้ไว้ให้ตัวเอง&lt;/strong&gt; เพราะเก็บ 3 อย่างที่ราคาวันนี้เกือบศูนย์ แต่แก้ทีหลังแพง&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;เก็บ hash ไม่เก็บตัว key&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;การ revoke ให้เก็บเป็นเวลา&lt;/strong&gt; แล้วกรองมันในตัว query ไม่ใช่ในโค้ด&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;อย่าใส่ UNIQUE บนคอลัมน์ &lt;code&gt;consumer&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;และตัดออก 4 อย่างที่ยังไม่ต้องมีใน Lite design — scopes, cache, CLI ออก key และ &lt;code&gt;last_used_at&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ขอบเขต Lite design
&lt;/h2&gt;

&lt;p&gt;ทั้งโพสต์พูดถึงกรณีเดียวคือ &lt;strong&gt;service to service&lt;/strong&gt; ไม่ใช่ key ที่User ปลายทางกดสร้างเองจากหน้าเว็บ&lt;/p&gt;

&lt;p&gt;สมมติฐานสามข้อนี้คือเหตุผลทั้งหมดที่ทำให้ design เล็กได้ขนาดนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ผู้ถือ key คือระบบ ไม่ใช่คน&lt;/strong&gt; — key นอนอยู่ใน secret manager ของอีกฝั่ง ไม่มีใครต้องจำ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;จำนวน consumer นับด้วยนิ้วมือได้&lt;/strong&gt; — consumer คือระบบฝั่งที่มาเรียกเรา ซึ่งในกรณีนี้คือทีมที่รู้จักชื่อกันทุกราย ไม่ใช่คนที่สมัครเข้ามาเองได้&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;คนออก key คือคุณ&lt;/strong&gt; — เป็นขั้นตอนที่มีคนกดเอง ไม่ใช่ flow อัตโนมัติ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้าเป็น public API ที่ใครก็สมัครได้ สมมติฐานพังทั้งสามข้อ และจะต้องมีการ rate limit ต่อราย, หน้าจัดการ key, วันหมดอายุ และการแยกสิทธิ์ ซึ่งอยู่นอกขอบเขตนี้&lt;/p&gt;

&lt;p&gt;และถ้าเบากว่านั้นอีก คือมี consumer แค่หนึ่งถึงสามรายที่แทบไม่เปลี่ยน ก็ไม่ต้องมีตาราง เก็บ hash ไว้ใน env ไปเลย แล้วโหลดเข้า Map ตอน boot พอ ที่เหลือในโพสต์นี้ใช้ได้เหมือนเดิมทั้งหมด ต่างแค่ที่มาของข้อมูล — แลกกับข้อเดียวคือรายชื่อ key ถูกอ่านตอน service เริ่มทำงาน การถอน key ออกจึงต้อง deploy ใหม่เท่านั้น&lt;/p&gt;

&lt;p&gt;ตัวตัดสินมีข้อเดียว: &lt;strong&gt;ถ้า key รั่วตอนตีสาม รับได้ไหมที่จะต้อง deploy เพื่อปิดมัน&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ถ้าตอนนี้ service ยังไม่มีอะไรกั้นเลย อย่ารอทำแบบเต็มให้เสร็จ ทำแบบ env ปิดช่องไปก่อนแล้วค่อยย้าย — ถ้าใช้สูตร hash เดียวกัน การย้ายไม่ต้องออก key ใหม่สักตัว&lt;/p&gt;

&lt;h2&gt;
  
  
  แล้วเมื่อไรที่ต้องการ feature ที่หนักกว่านี้
&lt;/h2&gt;

&lt;p&gt;design นี้จงใจไม่มี scopes, cache, CLI ออก key และคอลัมน์อย่าง &lt;code&gt;expires_at&lt;/code&gt; — แต่ละอย่างมีสัญญาณบอกเองว่าเมื่อไหร่ถึงเวลา คือ &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;scopes&lt;/strong&gt; เมื่อ endpoint หลัง guard เริ่มเสี่ยงไม่เท่ากัน ตัวที่เสียเงินต่อครั้งปนกับตัวที่แค่อ่านข้อมูล&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cache&lt;/strong&gt; เมื่อ DB lookup เริ่มกินเวลาจนเห็นในกราฟ latency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt; เมื่อออก key บ่อยจนเริ่มมีคนลัดขั้นตอน&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;expires_at&lt;/code&gt; / &lt;code&gt;issued_by&lt;/code&gt;&lt;/strong&gt; เมื่อต้องกำหนดวันหมดอายุหรือต้องตอบ audit ว่าใครออก key ไหนเมื่อไหร่&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ทั้งหมดเพิ่มทีหลังได้โดยไม่กระทบของเดิม ไม่ต้องออก key ใหม่ มีอย่างเดียวที่ย้อนแก้ยากคือตำแหน่งของ UNIQUE ในตาราง ซึ่งเป็นเหตุผลที่หัวข้อถัดไปให้พื้นที่กับมันมากกว่าเรื่องอื่น&lt;/p&gt;

&lt;h2&gt;
  
  
  การเก็บข้อมูล
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;client_api_key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;consumer&lt;/span&gt;    &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- ห้าม UNIQUE (ดูด้านล่าง)&lt;/span&gt;
  &lt;span class="n"&gt;key_hash&lt;/span&gt;    &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- sha256 hex ของ key เต็ม&lt;/span&gt;
  &lt;span class="n"&gt;revoked_at&lt;/span&gt;  &lt;span class="nb"&gt;DATETIME&lt;/span&gt;     &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                 &lt;span class="c1"&gt;-- เลขตั๋ว + ใครเป็นคนขอ&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;  &lt;span class="nb"&gt;DATETIME&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt;  &lt;span class="nb"&gt;DATETIME&lt;/span&gt;     &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;หกคอลัมน์นี้ดูธรรมดา แต่มีสามจุดที่เป็นการตัดสินใจ ไม่ใช่ความบังเอิญ&lt;/p&gt;

&lt;h3&gt;
  
  
  เก็บ hash ไม่เก็บตัว key
&lt;/h3&gt;

&lt;p&gt;ถ้าเก็บ key ตรงๆ ใครก็ตามที่อ่าน DB ได้จะกลายเป็นเจ้าของ key ทั้งหมดทันที และนั่นไม่ใช่แค่คนที่ตั้งใจโขมย — เป็นไปได้ว่ามี case แบบ Dev dump ของ prod ค้างอยู่บนเครื่อง dev เป็นเดือน เพราะจะ debug อะไรสักอย่างแล้วลืมลบ มันก็เป็นความเสี่ยงเหมือนกัน&lt;/p&gt;

&lt;p&gt;เก็บ &lt;code&gt;sha256(key)&lt;/code&gt; แทน แล้วเวลา authenticate ก็ hash ค่าที่รับมาแล้วค้นด้วย hash ไม่ต้องใช้ bcrypt เหมือนตอนเราเก็บ password เพราะค่าที่สุ่มมา 256 บิตไม่มีอะไรให้เดา และ salt จะทำให้ค้นด้วย index ไม่ได้&lt;/p&gt;

&lt;p&gt;ต้นทุนของการทำถูกวันนี้คือ code หนึ่งบรรทัด ต้นทุนของการมาแก้ทีหลังคือ key ที่เคยเก็บเป็น plain text ต้องถือว่ารั่วไปแล้วทุกตัว จึงต้องออกใหม่ทั้งหมดและนัดกับ consumer ทุกรายให้เปลี่ยนพร้อมกัน&lt;/p&gt;

&lt;h3&gt;
  
  
  การ revoke ให้เก็บเป็นเวลา
&lt;/h3&gt;

&lt;p&gt;เก็บเป็น timestamp เพราะตอบได้ทั้งว่า &lt;em&gt;ถูก revoke ไหม&lt;/em&gt; และ &lt;em&gt;เมื่อไหร่&lt;/em&gt; ซึ่งข้อหลังเป็นคำถามแรกในวันที่มีเรื่องให้ต้องสอบสวน &lt;/p&gt;

&lt;p&gt;แต่เรื่องที่สำคัญกว่าชื่อคอลัมน์คือ&lt;strong&gt;เอาเงื่อนไขนี้ไปวางไว้ตรงไหน&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;client_api_key&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;key_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;revoked_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ท่อน AND revoked_at IS NULL ต้องอยู่ในตัว query ไม่ใช่กรองในโค้ดทีหลัง เพราะ query ที่ขาดท่อนนี้จะคืนค่าถูกต้อง เทสผ่านหมด และไม่มีอะไรให้สะดุดตาตอน review — จนถึงวันที่กด revoke แล้วพบว่า key นั้นยังใช้ได้อยู่ ผมเคยปล่อยแบบนี้ผ่านมาแล้ว&lt;/p&gt;

&lt;p&gt;และถ้ากรองในโค้ด วันหนึ่งจะมีคนเขียน query ที่สองโดยไม่รู้ว่าต้องกรอง&lt;/p&gt;

&lt;h3&gt;
  
  
  อย่าใส่ UNIQUE บนคอลัมน์ &lt;code&gt;consumer&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;ครั้งแรกที่ผมทำตารางนี้ ผมใส่ UNIQUE บน &lt;code&gt;consumer&lt;/code&gt; ไปด้วยความรู้สึกว่ามันสะอาดดี หนึ่ง consumer หนึ่ง key ฐานข้อมูลช่วยการันตีให้ ไม่มีแถวขยะ&lt;/p&gt;

&lt;p&gt;สิ่งที่ UNIQUE ตรงนี้แปลว่า&lt;strong&gt;คุณหมุน key (key rotation) ไม่ได้&lt;/strong&gt; เพราะ key rotation ที่ปลอดภัยต้องมีช่วงที่ key เก่ากับใหม่ใช้ได้พร้อมกัน ถ้าถือได้ key เดียว ทางเดียวที่เหลือคือลบของเก่าแล้ว insert ของใหม่ แล้วทุก request ที่ยิงเข้ามาระหว่างนั้นจะโดน 401&lt;/p&gt;

&lt;p&gt;ช่วงเวลานั้นยาวแค่ไหนไม่ได้ขึ้นกับคุณ มันจบเมื่ออีกทีม deploy ค่าใหม่เสร็จ ซึ่งอาจเป็นบ่ายพรุ่งนี้หรือหลัง sprint นี้ — และมันจะเจ็บที่สุดในวันที่ key รั่วแล้วต้องเปลี่ยนเดี๋ยวนี้ ตอนนั้น UNIQUE จะบังคับให้เลือกระหว่างปล่อยให้ key ที่รั่วใช้ได้ต่ออีกพัก กับทำให้ consumer ล่ม ทั้งที่มันไม่ควรเป็นทางเลือกที่ต้องเจอตั้งแต่แรก&lt;/p&gt;

&lt;p&gt;ตุ้นทุนของการทำถูกวันนี้คือไม่พิมพ์คำว่า UNIQUE ตุ้นทุนของการมาแก้ทีหลังคือ drop index บนตารางที่มี traffic วิ่งอยู่&lt;/p&gt;

&lt;p&gt;เมื่อปล่อยให้ consumer ซ้ำได้แล้ว key rotation จะเหลือแค่นี้ ไม่มี downtime และ Rollback ได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT key ใหม่ → consumer สลับไปใช้ key ใหม่ → ยืนยันว่า key เก่าไม่มี traffic แล้ว → revoke key เก่า
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;key rotation เป็นคุณสมบัติที่ดีในงานความปลอดภัยของระบบที่จะมีได้&lt;/p&gt;

&lt;h2&gt;
  
  
  รูปแบบของ key
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acme_prod_9b1d4e7a…    (64 hex)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ส่วนนำหน้า &lt;code&gt;acme_&amp;lt;env&amp;gt;_&lt;/code&gt; ไม่มีผลต่อการตรวจสอบ เราไม่ได้ parse มัน มันมีไว้สองอย่าง&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ตั้ง pattern &lt;code&gt;acme_(dev|staging|prod)_[0-9a-f]{64}&lt;/code&gt; ใน secret scanner แล้วดักตอน commit ก่อน key จะขึ้นไปอยู่บน remote ตลอดกาล&lt;/li&gt;
&lt;li&gt;เวลามีคนแปะ key มาในแชทว่า "อันนี้ใช้ไม่ได้" จะเห็นคำตอบจากตัวอักษรสิบตัวแรกโดยไม่ต้องถามกลับ&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ต่อกับ framework
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request → จุดต่อ (อ่าน header) → resolver (hash → query DB) → ผ่าน หรือ 401
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;แยกเป็นสองชิ้น — &lt;strong&gt;resolver&lt;/strong&gt; ฟังก์ชันเดียวที่รับ key แล้วบอกว่าเป็นของใคร หรือคืน null ถ้าใช้ไม่ได้ และ &lt;strong&gt;จุดต่อ&lt;/strong&gt; ที่ framework เรียก ซึ่งจะชื่อ middleware, guard หรือ interceptor ก็แล้วแต่ค่าย แยกแบบนี้แล้วเปลี่ยนวิธีเก็บ key ทีหลังได้โดยไม่ต้องแตะโค้ดที่ผูกกับ framework&lt;/p&gt;

&lt;h3&gt;
  
  
  สัญญา 5 ข้อที่ห้ามเพี้ยน
&lt;/h3&gt;

&lt;p&gt;5 ข้อนี้ต้องเหมือนกันทุกที่ ไม่งั้น key ที่ออกจากที่หนึ่งจะใช้กับอีกที่ไม่ได้ หรือ revoke แล้วไม่ได้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;hash เป็น sha256 hex ตัวพิมพ์เล็ก ไม่มี newline ต่อท้าย&lt;/strong&gt; — สิ่งเดียวที่ทำให้ key เดิมยังใช้ได้ตอนย้ายวิธีเก็บ ระวัง &lt;code&gt;echo&lt;/code&gt; ใน shell ที่เติม &lt;code&gt;\n&lt;/code&gt; ให้โดยไม่บอก&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter "revoked_at IS NULL" ที่ชั้น lookup&lt;/strong&gt; — ตามหัวข้อที่แล้ว&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Http status 401 ทุกเคส&lt;/strong&gt; — ไม่มี header, key ไม่รู้จัก, key ถูก revoke และ body ไม่บอกเหตุผล ระวังว่าบาง framework ตีความ guard ที่คืนค่า false เป็น &lt;strong&gt;403&lt;/strong&gt; ซึ่งผิดความหมาย (401 คือ "ไม่รู้ว่าคุณคือใคร" ส่วน 403 คือ "รู้ว่าคุณคือใคร แต่เข้าตรงนี้ไม่ได้")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;log ชื่อ consumer ทุกครั้งที่ผ่าน และห้าม log ค่า key&lt;/strong&gt; — รวมถึงตอน error ซึ่งเป็นตอนที่คนเผลอที่สุด บรรทัดนี้จะเป็นแหล่งเดียวที่ตอบได้ว่า key ไหนยังมีคนใช้&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;แปะ consumer ลง request context&lt;/strong&gt; — ให้ layer ถัดไปใช้ต่อโดยไม่ต้อง query ซ้ำ&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Resolver
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT consumer FROM client_api_key WHERE key_hash = ? AND revoked_at IS NULL LIMIT 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;   &lt;span class="c1"&gt;// { consumer } หรือ null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เท่านี้จริง ๆ และไม่ต้องมี &lt;code&gt;timingSafeEqual&lt;/code&gt; เพราะเราค้นด้วย hash ผ่าน unique index ไม่ได้เทียบสตริงทีละไบต์ จึงไม่มีขั้นตอนไหนให้จับเวลา&lt;/p&gt;

&lt;h3&gt;
  
  
  จุดต่อ
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api key rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// ห้าม log ค่า key&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;unauthorized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;             &lt;span class="c1"&gt;// 401 และหยุดตรงนี้&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`api key accepted consumer=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;attachToContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;สามเรื่องที่ framework อาจไม่ได้จัดการให้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;header ซ้ำได้&lt;/strong&gt; ค่าที่ได้จึงเป็น array ได้ ต้องเช็คก่อนใช้เอง&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ถ้าปฏิเสธแล้วไม่หยุดตรงนั้น&lt;/strong&gt; request ที่ควรถูกปฏิเสธจะหลุดเข้าไปทำงานจนจบ ทั้งที่ status เป็น 401&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ถ้า resolver โยน error&lt;/strong&gt; บาง framework รุ่นเก่าไม่จับ promise ที่ reject ให้ request จะค้างจน timeout&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ออก key
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"acme_prod_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256   &lt;span class="c"&gt;# เอา hash ไปใส่ SQL&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;                          &lt;span class="c"&gt;# ส่งค่านี้ให้ consumer ครั้งเดียว&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;printf '%s'&lt;/code&gt; ไม่ใช่ &lt;code&gt;echo&lt;/code&gt; — &lt;code&gt;echo&lt;/code&gt; เติมขึ้นบรรทัดใหม่ให้ ทำให้ได้ hash คนละตัวกับที่โค้ดคำนวณ ผมเสียเวลาไปพักใหญ่กับเรื่องนี้ตอนที่ key เพิ่งออกมาสด ๆ แล้วใช้ไม่ได้โดยไม่มีเหตุผล&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;client_api_key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'billing-service'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;hash ที่ได้&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PLAT-1234 · ขอโดย …'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;อย่ารวบด้วย &lt;code&gt;SHA2('&amp;lt;key&amp;gt;', 256)&lt;/code&gt; ใน SQL แม้จะสั้นกว่า เพราะค่า key เต็มจะไปโผล่ใน log ของฐานข้อมูลหลายที่ (query log, general log, binlog) ซึ่งคุณอาจไม่ได้เป็นคนดูแล ส่วน shell history ติดอยู่ดีทั้งสองทาง เคลียร์หลังใช้&lt;/p&gt;

&lt;p&gt;ตอนส่งให้อีกทีม ใส่ลง secret manager ของฝั่งเขาโดยตรงหรือส่งผ่านช่องที่หมดอายุได้ ไม่ใช่แชทหรืออีเมล — ปลายทางของค่านี้คือ config ของระบบ ไม่ใช่คน&lt;/p&gt;

&lt;p&gt;ทำแยกทุก environment และถ้าอยากได้ audit ว่าใครออก key ไหนเมื่อไหร่ ให้เก็บ INSERT เป็น seed ใน repo แล้วให้ git history ทำหน้าที่นั้นแทน — &lt;strong&gt;ค่า hash ลง git ได้ ค่า key ห้าม&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Revoke
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;client_api_key&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;revoked_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;มีผล&lt;strong&gt;ทันที&lt;/strong&gt; เพราะ resolver ค้น DB ใหม่ทุก request โดยไม่มี cache — เป็นจุดเดียวที่ดีไซน์เล็กชนะดีไซน์ใหญ่ วันที่ใส่ cache เข้าไป คุณกำลังเปลี่ยนสัญญาของคำว่า revoke จาก "ทันที" เป็น "ภายใน N วินาที" และต้องบอกทีมให้ชัดว่า N เท่ากับเท่าไร&lt;/p&gt;

&lt;h3&gt;
  
  
  "key นี้ยังมีคนใช้อยู่ไหม"
&lt;/h3&gt;

&lt;p&gt;คำถามนี้มาก่อนการ revoke เสมอ ตอนเจอตารางที่มี key ค้างอยู่เจ็ดแถวและไม่มีใครกล้าลบ&lt;/p&gt;

&lt;p&gt;ตารางไม่มี &lt;code&gt;last_used_at&lt;/code&gt; โดยตั้งใจ เพราะมันแปลว่าต้อง UPDATE ทุก request ที่ผ่าน คือเปลี่ยน request ที่อ่านล้วนให้กลายเป็นการเขียน เพื่อข้อมูลที่ดูปีละครั้ง ตอบจาก log แทน เพราะ guard log ชื่อ consumer ทุก request ที่ผ่านอยู่แล้ว ถ้าใช้ Loki ก็ประมาณนี้ — เครื่องมืออื่นมีคำสั่งเทียบเคียง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nb"&gt;count_over_time&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-service"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`api key accepted consumer=billing-service`&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;30d&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ข้อจำกัดคือคำตอบเชื่อได้ไม่เกิน retention ของ log ถ้าเป็น batch ที่รันเดือนละครั้งและ log เก็บ 14 วัน จะสรุปผิดว่าไม่มีใครใช้แล้ว เคสแบบนั้นให้ revoke แบบมีทางถอย คือ revoke แล้วรอ ถ้าไม่มีใครร้องค่อยลบ&lt;/p&gt;

&lt;h2&gt;
  
  
  สรุป
&lt;/h2&gt;

&lt;p&gt;เก็บ hash ไม่เก็บ key, revoke เก็บเป็นเวลาแล้วกรองในตัว query, ไม่ใส่ UNIQUE บน consumer&lt;/p&gt;

</description>
      <category>backenddevelopment</category>
      <category>webdev</category>
      <category>microservices</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How to Register Permissions in Strapi Plugin</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Thu, 31 Oct 2024 12:36:49 +0000</pubDate>
      <link>https://dev.to/m4r14/how-to-register-permissions-in-a-stripe-plugin-17gj</link>
      <guid>https://dev.to/m4r14/how-to-register-permissions-in-a-stripe-plugin-17gj</guid>
      <description>&lt;p&gt;Registering permissions in a Strapi plugin helps manage user access to specific actions. Here’s a quick guide:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Permission Actions
&lt;/h3&gt;

&lt;p&gt;In bootstrap.ts, define the permission action:&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;// src/plugins/exporter/server/bootstrap.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Strapi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&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="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;strapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Strapi&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;registerPermissionActions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registerPermissionActions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actions&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="na"&gt;section&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Add Permissions to the Admin Menu
&lt;/h3&gt;

&lt;p&gt;In index.tsx, link the permission to the admin interface:&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;// src/plugins/exporter/admin/src/index.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMenuLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// other properties&lt;/span&gt;
      &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin::exporter.export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Format: plugin::plugin-name.actionType&lt;/span&gt;
          &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;These steps allow you to register and manage permissions effectively within your Strapi plugin.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>strapi</category>
    </item>
    <item>
      <title>Add Rest connector to Camunda Desktop Modeler</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Mon, 15 Jan 2024 14:32:14 +0000</pubDate>
      <link>https://dev.to/m4r14/adding-the-rest-connector-in-camunda-desktop-modeler-4nmg</link>
      <guid>https://dev.to/m4r14/adding-the-rest-connector-in-camunda-desktop-modeler-4nmg</guid>
      <description>&lt;p&gt;Here are step-by-step instructions for adding the Rest Connector in Camunda Desktop Modeler&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;strong&gt;Camunda Marketplace&lt;/strong&gt; at &lt;a href="https://marketplace.camunda.com" rel="noopener noreferrer"&gt;marketplace.camunda.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Search for "&lt;strong&gt;rest&lt;/strong&gt;" and select the first result, or click on &lt;a href="https://marketplace.camunda.com/en-US/apps/415933/rest-connector" rel="noopener noreferrer"&gt;rest-connector&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;On the page, click "&lt;strong&gt;For SM&lt;/strong&gt;" and you will be directed to the GitHub repository. 
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnl7bt30meia2b12hey00.png" alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;In the GitHub repository, select the "element-templates" folder and download the two files from there.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbt18b0vgzoqsd30eizt.png" alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;Create a sub-directory named &lt;code&gt;.camunda/element-templates/&lt;/code&gt; in the directory where your BPMN model is saved on the local filesystem.&lt;/li&gt;
&lt;li&gt;Move the two downloaded files from the GitHub repository into the newly created &lt;code&gt;.camunda/element-templates/&lt;/code&gt; directory. 
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwoql9ez9x0q1tmaw0fd.png" alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;Reload (cmd+r) the Camunda Desktop Modeler to apply the changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw1k1gk8po4gjnl2ro3s.png" alt="Image description"&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t63jzqi3e25ko8h3szj.png" alt="Image description"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now, you should be able to use the Rest Connector in your BPMN diagrams. &lt;/p&gt;

</description>
      <category>camunda</category>
      <category>bpmn</category>
      <category>automation</category>
      <category>desktopmodeler</category>
    </item>
    <item>
      <title>Integrating NestJS with Camunda-Zeebe SaaS: A Step-by-Step Guide</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Tue, 02 Jan 2024 14:49:00 +0000</pubDate>
      <link>https://dev.to/m4r14/integrating-nestjs-with-camunda-zeebe-saas-a-step-by-step-guide-3f1k</link>
      <guid>https://dev.to/m4r14/integrating-nestjs-with-camunda-zeebe-saas-a-step-by-step-guide-3f1k</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In today's dynamic software development landscape, orchestrating workflows efficiently is crucial. &lt;a href="https://camunda.com/platform/zeebe/"&gt;Camunda-Zeebe&lt;/a&gt;, a cloud-native workflow automation platform, seamlessly integrates with NestJS, a powerful Node.js framework. In this tutorial, we'll explore how to set up and connect NestJS with Camunda-Zeebe SaaS, allowing you to manage workflows with ease.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before diving into the integration, make sure you have the following prerequisites in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js and npm installed&lt;/li&gt;
&lt;li&gt;NestJS application set up&lt;/li&gt;
&lt;li&gt;Camunda-Zeebe SaaS account&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting Up Zeebe Microservice
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;main.ts&lt;/code&gt; file, register the Zeebe microservice. This establishes the connection between your NestJS application and the Zeebe server.&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;// main.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZeebeServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nestjs-zeebe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;microservice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectMicroservice&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ZeebeServer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAllMicroservices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3002&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring ZeebeModule in AppModule
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;app.module.ts&lt;/code&gt; file, register the ZeebeModule and configure the connection options. Ensure you replace placeholders with your actual credentials and gateway address.&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.module.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZeebeModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ZeebeServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nestjs-zeebe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;ZeebeModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;gatewayAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;yourGatewayAddress&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;oAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://login.cloud.camunda.io/oauth/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zeebe.camunda.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;yourClientId&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;yourClientSecret&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other module configurations&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to replace &lt;code&gt;&amp;lt;yourGatewayAddress&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;yourClientId&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;yourClientSecret&amp;gt;&lt;/code&gt; with your actual Camunda-Zeebe SaaS credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting Workflow Instances
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;app.controller.ts&lt;/code&gt; file, use the Zeebe client to start a workflow instance.&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.controller.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZEEBE_CONNECTION_PROVIDER&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nestjs-zeebe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&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;AppController&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="nd"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ZEEBE_CONNECTION_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;)&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;zbClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZBClient&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="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;startWorkflow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CreateProcessInstanceResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zbClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createProcessInstance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;bpmnProcessId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Process_16ssnwv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Replace with your actual BPMN process ID&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Workflow variables&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other controller methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;Process_16ssnwv&lt;/code&gt; with your actual BPMN process ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscribing to Zeebe Events
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;app.controller.ts&lt;/code&gt; file, implement a Zeebe worker to subscribe to specific events in the workflow.&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.controller.ts&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&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;AppController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other controller methods&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ZeebeWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZeebeJob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZBWorker&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IInputVariables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ICustomHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IOutputVariables&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mail, Task variables&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Task worker business logic goes here&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Send mail, success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedVariables&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;Adjust the worker method (&lt;code&gt;mail&lt;/code&gt; in this case) to match your workflow's task name and implement the necessary business logic.&lt;/p&gt;

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

&lt;p&gt;Congratulations! You've successfully integrated NestJS with Camunda-Zeebe SaaS, allowing you to manage workflows effortlessly. This powerful combination opens up a world of possibilities for orchestrating complex business processes in a scalable and efficient manner.&lt;/p&gt;

&lt;p&gt;Feel free to explore further and adapt these steps to your specific use case. Happy coding!&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>camunda</category>
      <category>zeebe</category>
      <category>orchestration</category>
    </item>
    <item>
      <title>Awesome Design patterns js/ts</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sun, 15 Oct 2023 01:58:07 +0000</pubDate>
      <link>https://dev.to/m4r14/awesome-design-patterns-jsts-20ff</link>
      <guid>https://dev.to/m4r14/awesome-design-patterns-jsts-20ff</guid>
      <description></description>
      <category>javascript</category>
      <category>programming</category>
      <category>typescript</category>
      <category>node</category>
    </item>
    <item>
      <title>Handling Multiple Schemas in NestJS Swagger with @ApiProperty 🚀</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sun, 24 Sep 2023 03:36:28 +0000</pubDate>
      <link>https://dev.to/m4r14/handling-multiple-schemas-in-nestjs-swagger-with-apiproperty-1ph8</link>
      <guid>https://dev.to/m4r14/handling-multiple-schemas-in-nestjs-swagger-with-apiproperty-1ph8</guid>
      <description>&lt;p&gt;Swagger simplifies API documentation, but handling multiple schemas can be challenging. In NestJS, you can use the &lt;code&gt;@ApiProperty&lt;/code&gt; decorator to tackle this. Let's explore this in a scenario. 🧐&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario 📜
&lt;/h2&gt;

&lt;p&gt;Imagine building an e-commerce platform with NestJS. You have an endpoint to update orders, which can contain an array of products. These products can be newly created or updated, requiring two schemas. 🛒📦&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Product Schemas 📝
&lt;/h2&gt;

&lt;p&gt;Start by defining product schemas. You have a &lt;code&gt;Product&lt;/code&gt; class with &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;quantity&lt;/code&gt;. Create two DTOs: &lt;code&gt;CreateProductDto&lt;/code&gt; and &lt;code&gt;UpdateProductDto&lt;/code&gt; for product creation and updates. 📋🛍️&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Properties&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProductDto&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;OmitType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateProductDto&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Multiple Schemas 🤯
&lt;/h2&gt;

&lt;p&gt;Now, let's handle multiple schemas within the &lt;code&gt;UpdateOrderRequestDto&lt;/code&gt;, representing an order update request. This DTO includes an array of products, which can be of either &lt;code&gt;CreateProductDto&lt;/code&gt; or &lt;code&gt;UpdateProductDto&lt;/code&gt;. 🔄📑&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiExtraModels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateProductDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UpdateProductDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateOrderRequestDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Properties&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiProperty&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;oneOf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSchemaPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateProductDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSchemaPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UpdateProductDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;value&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;// Transformation logic&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateProductDto&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;UpdateProductDto&lt;/span&gt;&lt;span class="p"&gt;)[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;@ApiExtraModels&lt;/code&gt; to include &lt;code&gt;CreateProductDto&lt;/code&gt; and &lt;code&gt;UpdateProductDto&lt;/code&gt; in Swagger documentation. 📚✨&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;UpdateOrderRequestDto&lt;/code&gt;, use &lt;code&gt;@ApiProperty&lt;/code&gt; to define an array with a choice between &lt;code&gt;CreateProductDto&lt;/code&gt;and &lt;code&gt;UpdateProductDto&lt;/code&gt; using &lt;code&gt;oneOf&lt;/code&gt;. 📊🧩&lt;/li&gt;
&lt;li&gt;Utilize &lt;code&gt;@Transform&lt;/code&gt; to conditionally transform data based on the id property for creation or update. 🔄🔀&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Handling multiple schemas in NestJS Swagger enhances API documentation for complex data structures. By leveraging &lt;code&gt;@ApiProperty&lt;/code&gt;, you can clearly define schemas, making your API documentation precise and comprehensible. 📖🚀&lt;/p&gt;

&lt;p&gt;This approach simplifies API development with Swagger, resulting in well-documented and robust APIs. Mastering Swagger's features is key to creating efficient APIs that streamline your development workflow. 🛠️👨‍💻&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>swagger</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>Do you prefer Calculus within the service class or Creating separate classes for calculations?</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Mon, 28 Aug 2023 15:05:45 +0000</pubDate>
      <link>https://dev.to/m4r14/do-you-prefer-calculus-within-the-service-class-or-creating-separate-classes-for-calculations-2d5c</link>
      <guid>https://dev.to/m4r14/do-you-prefer-calculus-within-the-service-class-or-creating-separate-classes-for-calculations-2d5c</guid>
      <description>&lt;h3&gt;
  
  
  Calculus within the service class:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Calculus in service classes:&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getAllAndSumary&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&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;itemRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subItems&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;subItemsRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allBy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;In&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&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;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&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="p"&gt;})&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&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;d&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countSubItemsBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subItems&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;sub&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;d&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="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumSubItemsBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subItems&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;sub&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;d&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="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;countSubItemsBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SubItem&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// logic &lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;sumSubItemsBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SubItem&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// logic &lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating separate classes for calculations:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemSummary&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;private&lt;/span&gt; &lt;span class="nx"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_subItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&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;_item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;count&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;_subItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countBy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;parentId&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;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;total&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;_subItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumBy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;parentId&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;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;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getAllAndSumary&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&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;itemRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subItems&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;subItemsRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allBy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;In&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&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;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&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="p"&gt;})&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&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;d&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ItemSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subItems&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;



</description>
      <category>coding</category>
      <category>programming</category>
    </item>
    <item>
      <title>Efficient Data Validation in Nestjs using class-validator: A Quick Guide</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sun, 27 Aug 2023 07:50:41 +0000</pubDate>
      <link>https://dev.to/m4r14/efficient-data-validation-in-nestjs-using-class-validator-a-quick-guide-2ff</link>
      <guid>https://dev.to/m4r14/efficient-data-validation-in-nestjs-using-class-validator-a-quick-guide-2ff</guid>
      <description>&lt;p&gt;Nestjs is a Node.js framework for building efficient server-side apps. It uses TypeScript and integrates class-validator for data validation. To validate DTOs, which define data shapes, you add decorators like &lt;code&gt;@IsEmail()&lt;/code&gt; or &lt;code&gt;@IsNotEmpty()&lt;/code&gt; to class properties. For instance, a CreateUserDto class might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IsEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IsString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-validator&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;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="nd"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;password&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="nd"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;To globally apply validation, use the &lt;code&gt;ValidationPipe&lt;/code&gt; in your &lt;code&gt;main.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ValidationPipe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useGlobalPipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationPipe&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For testing, use class-validator's functions like &lt;code&gt;plainToClass&lt;/code&gt; and &lt;code&gt;validateSync&lt;/code&gt;. A Jest unit test example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;plainToClass&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-transformer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validateSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./create-user.dto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CreateUserDto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should be valid with valid properties&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUserDto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plainToClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateUserDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createUserDto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should be invalid with invalid properties&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUserDto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plainToClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateUserDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createUserDto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Check error details...&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;Nestjs and class-validator simplify building and testing apps by using decorators to validate data. For more info, see the official Nestjs and class-validator documentation.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Effortless Slug Handling with NestJS Pipes</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sat, 26 Aug 2023 15:37:49 +0000</pubDate>
      <link>https://dev.to/m4r14/effortless-slug-handling-with-nestjs-pipes-1nab</link>
      <guid>https://dev.to/m4r14/effortless-slug-handling-with-nestjs-pipes-1nab</guid>
      <description>&lt;p&gt;NestJS pipes are essential for data validation and transformation. They are used to preprocess incoming data before it reaches your controller methods. In this example, we'll use pipes to validate and transform incoming slug parameters.&lt;/p&gt;

&lt;p&gt;Consider the following code snippet:&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;// Import required modules&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Param&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SlugValidationPipe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./slug-validation.pipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Article&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./article.entity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&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;ArticleController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;getArticleBySlug&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SlugValidationPipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;article&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;In this code, we define a route parameter named &lt;code&gt;slug&lt;/code&gt; using the &lt;code&gt;@Param()&lt;/code&gt; decorator. The &lt;code&gt;SlugValidationPipe&lt;/code&gt; validates and transforms the slug before passing it to the &lt;code&gt;getArticleBySlug&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Here's how the &lt;code&gt;SlugValidationPipe&lt;/code&gt; works:&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;// SlugValidationPipe.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BadRequestException&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ArticleService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./article.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&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;SlugValidationPipe&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&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;private&lt;/span&gt; &lt;span class="nx"&gt;articleService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ArticleService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;articleService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getArticleBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BadRequestException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Article not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;article&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;Inside the &lt;code&gt;SlugValidationPipe&lt;/code&gt;, customize validation and transformation logic as needed. This example assumes an &lt;code&gt;ArticleService&lt;/code&gt; with a &lt;code&gt;getArticleBySlug&lt;/code&gt; method to retrieve articles using slugs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>nestjs</category>
      <category>backend</category>
    </item>
    <item>
      <title>Handling Entity Not Found Using Pipes in NestJS</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sat, 26 Aug 2023 15:12:57 +0000</pubDate>
      <link>https://dev.to/m4r14/handling-entity-not-found-using-pipes-in-nestjs-2g4n</link>
      <guid>https://dev.to/m4r14/handling-entity-not-found-using-pipes-in-nestjs-2g4n</guid>
      <description>&lt;p&gt;In NestJS, pipes are essential for data validation and transformation. They also help in handling errors gracefully. Let's focus on using pipes to tackle the issue of entity not found, with a simple example.&lt;/p&gt;

&lt;p&gt;Imagine you're building an API with an endpoint to retrieve an item by its ID. If the item doesn't exist, you want to return an appropriate error response. Here's how you can do it using a custom pipe called &lt;strong&gt;EntityExistsPipe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, create the &lt;strong&gt;EntityExistsPipe&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NotFoundException&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ItemsService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./items.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&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;EntityExistsPipe&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&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;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;itemsService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ItemsService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;transform&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;itemsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItemById&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Item not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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;Next, apply the pipe to your route handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NotFoundException&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ItemsService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./items.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EntityExistsPipe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./entity-exists.pipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&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;ItemsController&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;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;itemsService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ItemsService&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="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EntityExistsPipe&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;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;itemsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItemById&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup, the &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt;&lt;/strong&gt; decorator binds the &lt;strong&gt;id&lt;/strong&gt; parameter to the &lt;strong&gt;getItem&lt;/strong&gt; method in the &lt;strong&gt;ItemsController&lt;/strong&gt;. The &lt;strong&gt;EntityExistsPipe&lt;/strong&gt; is used as the parameter decorator and checks if the item exists using the &lt;strong&gt;ItemsService&lt;/strong&gt;. If the item doesn't exist, a &lt;strong&gt;NotFoundException&lt;/strong&gt; is thrown.&lt;/p&gt;

&lt;p&gt;By using this approach, you can ensure that your API handles entity not found scenarios elegantly, providing clear and consistent error responses.&lt;/p&gt;

&lt;p&gt;In a nutshell, NestJS pipes simplify error handling and enhance your API's robustness by encapsulating validation and error logic.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>backend</category>
      <category>nestjs</category>
    </item>
    <item>
      <title>🌟 Awesome Web tools! 🚀</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sat, 20 May 2023 10:19:43 +0000</pubDate>
      <link>https://dev.to/m4r14/check-out-these-awesome-web-tools-20h6</link>
      <guid>https://dev.to/m4r14/check-out-these-awesome-web-tools-20h6</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://damngood.tools/"&gt;Damn Good Tools&lt;/a&gt; Easy-to-use, fun productivity tools - free &amp;amp; open-source&lt;/li&gt;
&lt;li&gt;🎨 &lt;a href="https://app.diagrams.net/"&gt;Draw.io&lt;/a&gt; Create stunning diagrams effortlessly 📊&lt;/li&gt;
&lt;li&gt;📷 &lt;a href="https://codebeautify.org/code-to-image-converter"&gt;Code to Image Converter&lt;/a&gt; Transform your code snippets into eye-catching visuals 🖼️&lt;/li&gt;
&lt;li&gt;🌐 &lt;a href="https://wormhole.app/"&gt;Wormhole&lt;/a&gt; Share files and collaborate seamlessly 🔄&lt;/li&gt;
&lt;li&gt;🔍 &lt;a href="https://www.diffchecker.com/text-compare/"&gt;Diffchecker&lt;/a&gt; Compare text differences like a pro ↔️&lt;/li&gt;
&lt;li&gt;👻 &lt;a href="https://www.remove.bg/upload"&gt;Remove.bg&lt;/a&gt; Remove backgrounds from images in a snap 🖼️&lt;/li&gt;
&lt;li&gt;🦸 &lt;a href="https://jsonhero.io/"&gt;JsonHero&lt;/a&gt; Tame complex JSON structures with ease 🗂️&lt;/li&gt;
&lt;li&gt;🧜 &lt;a href="https://mermaid.live/"&gt;Mermaid&lt;/a&gt; Create beautiful diagrams and flowcharts effortlessly 🌊&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://chat.openai.com/"&gt;OpenAI Chat&lt;/a&gt; Have interactive conversations with an AI assistant 💬&lt;/li&gt;
&lt;li&gt;✨ &lt;a href="https://www.htmlwasher.com/"&gt;HTML Washer&lt;/a&gt; Clean up your HTML code for better readability 🧹&lt;/li&gt;
&lt;li&gt;🐘 &lt;a href="https://phpdocker.io/"&gt;PHPDocker.io&lt;/a&gt; Generate PHP development environments using Docker containers 🐳&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Boost your productivity and unleash your creativity with these amazing tools! 🚀💪 &lt;/p&gt;

</description>
      <category>webtools</category>
      <category>techgoodies</category>
      <category>productivitytools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🚀 Introducing GPT-Talk-GPT: Let GPT Chat with Each Other! 🤖💬</title>
      <dc:creator>mark vachi</dc:creator>
      <pubDate>Sun, 07 May 2023 11:44:57 +0000</pubDate>
      <link>https://dev.to/m4r14/introducing-gpt-talk-gpt-2ho4</link>
      <guid>https://dev.to/m4r14/introducing-gpt-talk-gpt-2ho4</guid>
      <description>&lt;p&gt;Introducing "gpt-talk-gpt", a new project that allows two GPT-3 models to talk to each other using OpenAI's powerful language processing capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔎 What is GPT-3?
&lt;/h3&gt;

&lt;p&gt;GPT-3 (Generative Pre-trained Transformer 3) is a state-of-the-art language processing model developed by OpenAI. It is capable of generating human-like text, completing sentences, translating languages, and even answering questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 What is gpt-talk-gpt?
&lt;/h3&gt;

&lt;p&gt;gpt-talk-gpt is a Python-based project that uses OpenAI's GPT-3 API to enable two GPT-3 models to communicate with each other. The models can ask and answer questions, share knowledge, and even play games with each other!&lt;/p&gt;

&lt;h3&gt;
  
  
  🕹️ Example: Guess the Word game
&lt;/h3&gt;

&lt;p&gt;One example of how gpt-talk-gpt can be used is by playing a "Guess the Word" game between two models. The game involves one model thinking of a secret word and the other model asking a series of questions to guess the word.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Enter the secret word: love
secret: love

Question 1 &lt;span class="o"&gt;(&lt;/span&gt;love&lt;span class="o"&gt;)&lt;/span&gt;:
+A:  Does the word have more than one syllable?
+B:  Yes.

&lt;span class="o"&gt;==========================================&lt;/span&gt;
Question 2 &lt;span class="o"&gt;(&lt;/span&gt;love&lt;span class="o"&gt;)&lt;/span&gt;:
+A:  Does the word start with the letter &lt;span class="s2"&gt;"S"&lt;/span&gt;?
+B:  No.

&lt;span class="o"&gt;==========================================&lt;/span&gt;
Question 3 &lt;span class="o"&gt;(&lt;/span&gt;love&lt;span class="o"&gt;)&lt;/span&gt;:
+A:  Does the word end &lt;span class="k"&gt;in &lt;/span&gt;the letter &lt;span class="s2"&gt;"y"&lt;/span&gt;?
B: Yes.
+B: 

&lt;span class="o"&gt;==========================================&lt;/span&gt;
Question 4 &lt;span class="o"&gt;(&lt;/span&gt;love&lt;span class="o"&gt;)&lt;/span&gt;:
+A:  Is the word related to an animal?
+B:  No.

&lt;span class="o"&gt;==========================================&lt;/span&gt;
Question 5 &lt;span class="o"&gt;(&lt;/span&gt;love&lt;span class="o"&gt;)&lt;/span&gt;:
+A:  Is the word a &lt;span class="nb"&gt;type &lt;/span&gt;of food?
+B:  No.
I won.

&lt;span class="o"&gt;==========================================&lt;/span&gt;
Question 6 &lt;span class="o"&gt;(&lt;/span&gt;love&lt;span class="o"&gt;)&lt;/span&gt;:
+A:  I give up. What is the word?
B: The word is &lt;span class="s2"&gt;"sunny"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
+B:  No. The word is &lt;span class="s2"&gt;"love"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nt"&gt;------------------------------------------&lt;/span&gt;
i give B the word love.
&lt;span class="nt"&gt;------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  🔗 Try it out!
&lt;/h3&gt;

&lt;p&gt;You can find the code for gpt-talk-gpt on our Github repository: &lt;a href="https://github.com/M4R14/gpt-talk-gpt"&gt;github.com/M4R14/gpt-talk-gpt&lt;/a&gt;. Feel free to download it, experiment with it, and contribute to it.&lt;/p&gt;

&lt;p&gt;👨‍💻 Let's create amazing things with GPT-3 and OpenAI!&lt;/p&gt;




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/M4R14"&gt;
        M4R14
      &lt;/a&gt; / &lt;a href="https://github.com/M4R14/gpt-talk-gpt"&gt;
        gpt-talk-gpt
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;h1&gt;
AI - Guess the Word Game&lt;/h1&gt;
&lt;p&gt;This is a Python console game where one player has to guess a secret word within a limited number of questions by asking yes or no questions to the other player. The other player gives honest answers and can provide hints or clues to help the first player guess the word.&lt;/p&gt;
&lt;h2&gt;
Getting Started&lt;/h2&gt;
&lt;p&gt;To play the game, you need Python 3.7 or higher installed on your computer. Clone or download this repository and run the &lt;code&gt;guess_the_word.py&lt;/code&gt; file in your Python environment.&lt;/p&gt;
&lt;h2&gt;
How to Play&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;At the beginning of the game, player A is informed of the rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A can ask a maximum of 5 questions.&lt;/li&gt;
&lt;li&gt;A cannot ask for a direct translation of the word.&lt;/li&gt;
&lt;li&gt;If A can guess the word within 5 questions, A wins.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Player B is informed of the rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;B must answer each question truthfully and to the best of…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/M4R14/gpt-talk-gpt"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
      <category>gpt3</category>
      <category>openai</category>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
