<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by richardevcom on Medium]]></title>
        <description><![CDATA[Stories by richardevcom on Medium]]></description>
        <link>https://medium.com/@richardevcom?source=rss-6f5a6c2cf7d1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*LkWtR0tk_TvHoESJs-tIig.png</url>
            <title>Stories by richardevcom on Medium</title>
            <link>https://medium.com/@richardevcom?source=rss-6f5a6c2cf7d1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 06:48:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@richardevcom/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Simbase API nightmare and how to avoid it]]></title>
            <link>https://medium.com/@richardevcom/simbase-api-maze-3a09ff32b3e3?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/3a09ff32b3e3</guid>
            <category><![CDATA[iot]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[mi]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Tue, 08 Jul 2025 12:01:50 GMT</pubDate>
            <atom:updated>2025-07-17T11:52:28.741Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B7aKdNnEYpIlAx8YmjtFSw.png" /></figure><h3>Simbase API maze and how to avoid it</h3><blockquote>A few months ago, our client decided to use Simbase for managing custom SIM cards in OBD devices. Little did I know I was about to dive headfirst into a debugging nightmare.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*CjPfDA0eorw1bZbZtiqYlA.jpeg" /></figure><blockquote>Too lazy to read it all? This post is mostly a rant — feel free to skip ahead to<br>👉 <a href="#8453"><strong>Epilogue: Why I’ll Never Use Simbase Again</strong></a>.</blockquote><h3>Issue #1 — Inefficient data retrieval</h3><p>At first, everything looked fine — API permissions were set up, IP whitelisting in place, and even webhook triggers (<em>though they never fired as promised</em>). Managing hundreds of SIM cards through their API seemed straightforward, so I thought, “<em>I’ll figure it out as I go.</em>”</p><p>Then reality hit. I discovered a major flaw: <strong>zero granular filtering</strong>. Our SIMs live in OBD devices and are tracked by IMEI. Need to fetch a SIM by IMEI? Forget it — you can only fetch by ICCID. That means pulling every single SIM and hunting for one needle in a haystack. I expected a direct lookup; instead, I got endless paginated requests. Multiply that by 100+ clients, and you’re guaranteed to hit those absurd rate limits. Uff! 🤦‍♂️</p><h4>The “Solution”</h4><p>I had no choice but to fetch, cache, and store all SIMs locally — and keep everything in sync. This extra layer was a ticking time bomb for our production environment.</p><pre>import axios from &#39;axios&#39;;<br>import { getLocalSims, saveLocalSims } from &#39;./localDatabase&#39;;<br><br>interface SimCard {<br>  iccid: string;<br>  imei: string;<br>  // ... other fields<br>}<br><br>interface SimcardsResponse {<br>  simcards: SimCard[];<br>  cursor: string | null;<br>}<br><br>export async function getSyncedSims(): Promise&lt;SimCard[]&gt; {<br>  const allSims: SimCard[] = [];<br>  let cursor: string | null = null;<br><br>  // keep pulling pages until cursor is null<br>  do {<br>    const { data } = await axios.get&lt;SimcardsResponse&gt;(<br>      &#39;https://api.simbase.com/v2/simcards&#39;,<br>      {<br>        headers: { Authorization: &#39;Bearer YOUR_SECRET_TOKEN&#39; },<br>        params: { cursor },<br>      }<br>    );<br>    allSims.push(...data.simcards);<br>    cursor = data.cursor;<br>  } while (cursor);<br><br>  await saveLocalSims(allSims);<br>  return allSims;<br>}<br><br>export async function getSyncedSimByImei(<br>  imei: string<br>): Promise&lt;SimCard | undefined&gt; {<br>  const sims = await getSyncedSims();<br>  return sims.find(sim =&gt; sim.imei === imei);<br>}</pre><h3>Issue #2: Absurd rate limits</h3><p>By the time I built our local SIM-management layer, I quickly ran into ridiculous Simbase’s limits:</p><ul><li><strong>10 calls per second;</strong></li><li><strong>5,000 calls per day</strong>;</li></ul><p>Juggling data for 100+ (<em>expected therefor simulated</em>) clients, every extra API call felt like pouring gasoline on a fire. 🤯</p><p>Syncing hundreds of SIMs? Torture. The only workaround was a homemade <strong>throttle</strong> + <strong>batch scheduler</strong> — more code, more delays, more potential headaches.</p><h4>The “Solution”</h4><p>I wrapped my fetch in a tiny rate-limiter class and batched the updates. It’s not pretty, but at least we don’t smash the API — or our sanity.</p><pre>// ...<br><br>const delay = (ms: number) =&gt; new Promise(res =&gt; setTimeout(res, ms));<br><br>class RateLimiter {<br>  private calls = 0;<br>  private windowStart = Date.now();<br><br>  constructor(private limit = 10, private windowMs = 1000) {}<br><br>  async throttle() {<br>    const now = Date.now();<br><br>    if (now - this.windowStart &gt;= this.windowMs) {<br>      this.windowStart = now;<br>      this.calls = 0;<br>    }<br><br>    if (this.calls &gt;= this.limit) {<br>      await delay(this.windowMs - (now - this.windowStart));<br>      this.windowStart = Date.now();<br>      this.calls = 0;<br>    }<br><br>    this.calls++;<br>  }<br>}<br><br>export async function getSyncedSims(): Promise&lt;SimCard[]&gt; {<br>  const limiter = new RateLimiter(10, 1000); // 10 calls/sec<br>  const allSims: SimCard[] = [];<br>  let cursor: string | null = null;<br><br>  do {<br>    await limiter.throttle();<br><br>    const { data } = await axios.get&lt;SimcardsResponse&gt;(<br>      &#39;https://api.simbase.com/v2/simcards&#39;,<br>      {<br>        headers: { Authorization: &#39;Bearer YOUR_SECRET_TOKEN&#39; },<br>        params: { cursor },<br>      }<br>    );<br><br>    allSims.push(...data.simcards);<br>    cursor = data.cursor;<br>  } while (cursor);<br><br>  await saveLocalSims(allSims);<br>  return allSims;<br>}<br><br>// ...</pre><h3>Issue #3 — Inconsistent field naming</h3><p>Next up — small but annoying inconvenience — inconsistent field names. Some endpoints return cost as current_month_cost; others use current_month_costs. It’s like the dev team flipped a coin every time—so you end up adding boilerplate just to keep your code sane.</p><h4>The “Solution”</h4><p>I wrote a tiny (<em>unnecessary</em>) normalizer that standardizes both variants into one cost property. No more guessing which field you’ll get.</p><pre>export function normalizeSimCard(sim: SimCardRaw): SimCardNormalized {<br>  return {<br>    iccid: sim.iccid,<br>    imei: sim.imei,<br>    cost: sim.current_month_cost || sim.current_month_costs || &quot;0&quot;,<br>    // ... normalize other fields as needed<br>  };<br>}</pre><h3>Issue #4 — Sparse error messaging</h3><p>Debugging became painful when the API started returning vague responses. One wrong call returned a vague 429 error and left me thinking: “<em>Was it the per-second cap or the daily limit?</em>” The message didn’t say.</p><h4>The “Solution”</h4><p>I wrapped every request in a helper that logs the error, reads retry_after, and automatically retries—or bails if I’ve truly hit the daily ceiling. With this in place, every 429 comes with clear context and an automatic pause — no more head-scratching over “<em>Which limit did I hit?</em>”</p><pre>// ...<br>const delay = (ms: number) =&gt; new Promise(res =&gt; setTimeout(res, ms));<br><br>/**<br> * Fetch with built-in retry for 429 errors.<br> * Logs context, waits the suggested time, and retries once.<br> */<br>export async function fetchWithRetry&lt;T&gt;(<br>  url: string,<br>  options: Record&lt;string, any&gt; = {}<br>): Promise&lt;T&gt; {<br>  try {<br>    const { data } = await axios.get&lt;T&gt;(url, options);<br>    return data;<br>  } catch (err) {<br>    if (axios.isAxiosError(err) &amp;&amp; err.response) {<br>      const { status, data } = err.response as { status: number; data: any };<br>      console.error(`Error ${status}:`, data.message || err.message);<br><br>      if (status === 429) {<br>        const waitSec = typeof data.retry_after === &#39;number&#39; ? data.retry_after : 10;<br>        if (waitSec &gt;= 86400) {<br>          console.error(&#39;Daily rate limit hit—stopping retries for today.&#39;);<br>          throw err;<br>        }<br>        console.log(`Rate limit hit. Waiting ${waitSec}s before retrying…`);<br>        await delay(waitSec * 1000);<br>        return fetchWithRetry(url, options);<br>      }<br>    }<br>    throw err; // non-retryable or non-Axios error<br>  }<br>}<br><br>// ...</pre><h3>Issue #5 — Bulk operations</h3><p>I finally had a basic API client working and decided to add bulk CRUD operations. In unit tests, it felt like playing <em>Russian roulette</em> — one failed SIM, and the whole batch imploded. What should have been a single call turned into a spaghetti of errors and wasted time.</p><h4>The “Solution”</h4><p>Use a “<em>bulk-first</em>” strategy with recursive <em>split-on-failure</em>. Try registering all SIMs at once. If it fails, split the list in half, retry each half, and keep recursing until only the bad record is isolated. That way, most SIMs sail through in efficient bulk calls, and only the failed ones get singled out — no need to fall back to <em>one-by-one</em> for every SIM.</p><pre>// ...<br><br>interface BulkSim {<br>  iccid: string;<br>  // ... other fields<br>}<br><br>async function registerBulk(sims: BulkSim[]): Promise&lt;void&gt; {<br>  await axios.post(&#39;https://api.simbase.com/v2/simcards/register&#39;, sims, {<br>    headers: { Authorization: `Bearer ${&#39;YOUR_SECRET_TOKEN&#39;}` },<br>  });<br>  console.log(`✔️ Registered ${sims.length} SIMs in bulk`);<br>}<br><br>export async function safeBulkRegister(sims: BulkSim[]): Promise&lt;void&gt; {<br>  try {<br>    await registerBulk(sims);<br>  } catch (err: any) {<br>    if (sims.length === 1) {<br>      console.error(<br>        `❌ SIM ${sims[0].iccid} failed:`,<br>        err.response?.data?.message || err.message<br>      );<br>      return;<br>    }<br>    const mid = Math.floor(sims.length / 2);<br>    await safeBulkRegister(sims.slice(0, mid));<br>    await safeBulkRegister(sims.slice(mid));<br>  }<br>}<br><br>// ...</pre><h3>Issue #6 — Flaky webhook callbacks</h3><p>Simbase promised real-time updates via webhooks; in practice these callbacks were as reliable as a paper umbrella in a storm — irregular, delayed, or vanishing into thin air. 🤷‍♂️ Without guarantees, you’ll miss critical events and have no clue why.</p><h4>The “Solution”</h4><p>Add a polling fallback that only fetches new events. Use a since cursor (<em>timestamp or event ID</em>) so each poll grabs just what you haven’t seen. Webhooks do the heavy lifting when they arrive; polling picks up anything they miss.</p><pre>interface SimEvent {<br>  id: string;<br>  timestamp: string;<br>  type: string;<br>  payload: any;<br>}<br><br>interface EventsResponse {<br>  events: SimEvent[];<br>}<br><br>const POLL_INTERVAL = 5 * 60 * 1000; // 5 minutes<br><br>async function pollEvents(lastTimestamp: string | null): Promise&lt;string | null&gt; {<br>  try {<br>    const params = lastTimestamp ? { since: lastTimestamp } : {};<br>    const { data } = await axios.get&lt;EventsResponse&gt;(&#39;https://api.simbase.com/v2/events&#39;, {<br>      headers: { Authorization: `Bearer ${&#39;YOUR_SECRET_TOKEN&#39;}` },<br>      params,<br>    });<br><br>    if (data.events.length) {<br>      console.log(&#39;✔️ Polled events:&#39;, data.events);<br>      // Return the newest event timestamp for next poll<br>      return data.events[data.events.length - 1].timestamp;<br>    }<br>  } catch (err: any) {<br>    console.error(&#39;Polling error:&#39;, err.response?.data?.message || err.message);<br>  }<br>  return lastTimestamp;<br>}<br><br>// Kick off polling<br>let lastTs: string | null = null;<br>(async () =&gt; {<br>  lastTs = await pollEvents(lastTs);<br>  setInterval(async () =&gt; {<br>    lastTs = await pollEvents(lastTs);<br>  }, POLL_INTERVAL);<br>})();</pre><h3>Additional inconvenience — Outdated documentation &amp; glacial support</h3><p>The official Simbase docs feel like relics from the API Stone Age — packed with abstract examples, missing endpoints, and inconsistent field names. I was lucky enough to start on v2, but if you’ve ever been stuck with v1, you’ll know it’s like navigating a maze.</p><p>When you finally need help — a quick rate-limit bump or clarification on a disappearing webhook event callback — their support won’t help you. My first email — ghosted, and the one follow-up reply to client e-mail took another week to arrive.</p><h3>Epilogue: Why I’ll Never Use Simbase API Again</h3><p>After getting lost in Simbase’s maze, I learned that peace of mind beats their inconvenient API features. Here’s why this turned into a nonstop debugging nightmare:</p><ol><li><strong>Inefficient data retrieval</strong>: No proper filters. Need a SIM by IMEI? You pull every SIM by ICCID and hunt it down yourself;</li><li><strong>Absurd rate limits</strong>: <strong><em>10 calls/sec</em></strong>,<em> </em><strong><em>5000 calls/day</em></strong> — constant throttling hacks and extra code to cope;</li><li><strong>Inconsistent field naming</strong>: current_month_cost here, current_month_costs there — pick one, please;</li><li><strong>Sparse error messaging</strong>: Vague codes forced me to build custom error handlers and retry loops;</li><li><strong>Bulk operations gone wild</strong>: One bad SIM sinks the entire batch;</li><li><strong>Flaky webhook callbacks</strong>: Promised live updates that almost never arrive without a polling backup;</li><li><strong>Outdated Documentation &amp; Slow Support</strong>: Documentation that’s more confusing than helpful, paired with glacial support.</li></ol><p>I wish we had chosen a better alternative at the time, but this was my first — and last — SIM management project with them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/448/1*_FNLSGpt93kbK9CquThYZA.gif" /></figure><p>In the end, this is more than a rant — it’s a cautionary tale. Save yourself the trouble, skip the endless maze, and steer clear of Simbase.</p><p><em>Thanks for reading this far and happy coding! 🙏</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a09ff32b3e3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[WordPress development environment with Docker]]></title>
            <link>https://medium.com/@richardevcom/wordpress-development-environment-with-docker-ba52427bdd65?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/ba52427bdd65</guid>
            <category><![CDATA[wordpress]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[containers]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Fri, 09 Aug 2024 12:09:53 GMT</pubDate>
            <atom:updated>2025-03-22T01:08:45.312Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R0Ht3gWvm795TLDXqOh0VA.png" /></figure><p><em>We built a WordPress site for a client ages ago. Fast forward to today, and we’re tasked with a much-needed update. The old process? Let’s just say it was about as fun as watching paint dry. I realized the painfully slow workflow was a major pain compared to modern development standards. I knew I had to create somewhat automated development environment.. and I chose to do that using Docker.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*V_aPP_OqFpE3JDjUgKdw7Q.png" /></figure><p>Setting up <em>WordPress</em> with <em>Docker</em> means you can avoid the hassle of setting up set of server-side software for web applications. Even though <em>Docker</em> isn’t made specifically for <em>WordPress</em>, the setup is rather simple. In this guide, we’ll look at how to setup <em>WordPress</em> development environment using <em>Docker</em>.</p><h3>Before you start</h3><ul><li>Make sure you have <em>Docker</em> installed. You can quickly <a href="https://docs.docker.com/get-docker/">get started with <em>Docker</em> here</a>.</li><li>⚠️ If you’re running <em>Linux</em> — you’ll also need to manually <a href="https://docs.docker.com/compose/install/">setup <em>Docker</em> <em>Compose</em></a>.</li></ul><h3>Project tree</h3><p>To keep things lean, we’ll avoid loading the entire <em>WordPress</em> source code into a shared volume. This can be a real resource hog! Instead, we’ll focus on sharing just the wp-content/ folder. Let&#39;s break down the project structure:</p><pre>wp-dev/<br>├─ wp-content/<br>├─ compose.yml<br>├─ dump.sql (optional)</pre><p>Go ahead and create the project tree like one above.</p><blockquote>⚡ If you have an existing WordPress project that you want to import (like I did), simply copy your wp-content folder and database dump file into the project&#39;s root directory.</blockquote><h3>Configuration</h3><p>Here is the compose.yml configuration file for <em>Docker Compose</em>:</p><pre>version: &quot;3.6&quot;<br>services:<br>  wordpress:<br>    image: wordpress:latest<br>    container_name: wordpress<br>    volumes:<br>      - ./wp-content:/var/www/html/wp-content<br>    environment:<br>      - WORDPRESS_DB_NAME=wordpress<br>      - WORDPRESS_TABLE_PREFIX=wp_<br>      - WORDPRESS_DB_HOST=db<br>      - WORDPRESS_DB_USER=root<br>      - WORDPRESS_DB_PASSWORD=password<br>    depends_on:<br>      - db<br>      - phpmyadmin<br>    restart: always<br>    ports:<br>      - 8080:80<br><br>  db:<br>    image: mariadb:latest<br>    container_name: db<br>    volumes:<br>      - db_data:/var/lib/mysql<br>      # This is optional!!!<br>      - ./dump.sql:/docker-entrypoint-initdb.d/dump.sql<br>      # # #<br>    environment:<br>      - MYSQL_ROOT_PASSWORD=password<br>      - MYSQL_USER=root<br>      - MYSQL_PASSWORD=password<br>      - MYSQL_DATABASE=wordpress<br>    restart: always<br><br>  phpmyadmin:<br>    depends_on:<br>      - db<br>    image: phpmyadmin/phpmyadmin:latest<br>    container_name: phpmyadmin<br>    restart: always<br>    ports:<br>      - 8180:80<br>    environment:<br>      PMA_HOST: db<br>      MYSQL_ROOT_PASSWORD: password<br><br>volumes:<br>  db_data:</pre><p>This is a standard configuration file to create a WordPress development environment. However, I’ve made a couple of adjustments to tailor it to our specific needs:</p><ul><li>volumes: - ./wp-content:/var/www/html/wp-content: this line syncs your local wp-content/ folder with the container&#39;s wp-content/ directory, allowing you to make changes locally and see them reflected in the container.</li><li>./dump.sql:/docker-entrypoint-initdb.d/dump.sql: will automatically import your database dump file after MySQL server is installed.</li></ul><h3>Running it</h3><p><em>👏 You’re almost there! With your </em><em>compose.yml file in place, it&#39;s time to bring your development environment to life.</em></p><p>Open your terminal and navigate to your project directory. Then, run the magic command:</p><pre>docker-compose up -d</pre><blockquote>⚡ This command will create and start all the containers defined in your compose.yml file in detached mode, meaning they&#39;ll run in the background without interrupting your terminal session.</blockquote><h3>Quick fix file permissions</h3><p><em>Before we dive into your new WordPress development environment, there’s one small adjustment to make. Docker has specific rules about file permissions, and the WordPress image is set up in a particular way. To ensure you can easily edit your WordPress files from outside the container, you’ll need to change the ownership of the </em><em>wp-content/ directory.</em></p><p>Open your terminal and run the following command, replacing {your-username} with your actual username:</p><pre>sudo chown -R {your-username}:{your-username} wp-content</pre><blockquote>⚡ This command will give you the necessary permissions to work with your WordPress files without any hassle.</blockquote><h3>Ready!</h3><p>🥳 Congratulations! Your <em>Dockerized WordPress</em> development environment is up and running.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/499/1*n-GZW698RhCLobDzcwcveQ.gif" /></figure><p>To access your new WordPress installation, open your web browser and navigate to localhost:8080. You&#39;ll be greeted by the familiar <em>WordPress</em> installation screen (<em>if you imported an existing project source, you’ll see your homepage</em>).</p><p>Follow the on-screen instructions to complete the setup process. Once you&#39;ve finished, you&#39;ll be able to log in to your <em>WordPress</em> admin dashboard using the credentials you created.</p><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ba52427bdd65" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[‘memba?: Twilio & Vertex AI SMS Reminder System for Seniors who don’t ‘memba.]]></title>
            <link>https://towardsdev.com/memba-twilio-vertex-ai-reminder-system-for-seniors-who-dont-memba-466ab033bfa5?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/466ab033bfa5</guid>
            <category><![CDATA[mysql]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[prisma]]></category>
            <category><![CDATA[twilio]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Mon, 24 Jun 2024 19:25:02 GMT</pubDate>
            <atom:updated>2025-03-22T01:09:18.745Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SxqTyH9e-R8I8zcP1L357Q.png" /></figure><p><em>✨ As part of dev.to Twilio AI challange me and @isitayush crafted a digital guardian angel for seniors (not you… probably).</em></p><h3>Here’s how ‘memba works:</h3><p>1. You simply send an SMS message to a designated Twilio phone number +44 7822 021078. The message should be formatted like this: Remind me to take medicine on 24nd June 18:00.</p><p>2. Twilio sends SMS to our Node.js/Express.js app container.</p><blockquote>⚡ This app is containerized using Docker Hub and run using Google Cloud Run (hail automation).</blockquote><p>3. The Cloud Run application forwards the SMS content to Vertex AI (Gemini LLM), with our predefined instructions.</p><pre>Functionality:Parse reminder text into JSON format.<br>Input:The text of the reminder and/or date, time.Output:JSON string in the format: {&#39;reminder_text&#39;: &quot;&lt;text&gt;&quot;, &#39;reminder_datetime&#39;: &quot;&lt;ISO 8601 extended format&gt;&quot;}.<br>Don&#39;t include any introduction text likt &quot;Hello…&quot;, &quot;Remind me to…&quot;, &quot;Please…&quot; in reminder_text.<br>Error handling:If the reminder text doesn&#39;t follow the expected format (e.g., missing date/time), return an empty JSON string {}.<br>If the date/time is not set or can&#39;t be parsed, return an empty JSON string {}.<br>If reminder_text is empty or not set, return an empty JSON string {}.<br>If date is set, but time is not set, use current time. If time is set, but date is not set, use current date.<br>Don&#39;t return the JSON string as markdown.<br>Do not delimit the output JSON with anything.<br>If reminder text contains abstract date, time meanings like &quot;tomorrow&quot;, &quot;next week&quot;, &quot;next month&quot;, etc. then convert them to date, time as ISO8601 extended format - for example, &quot;tomorrow&quot; becomes &lt;current_date&gt; + 1 day or &quot;next week&quot; becomes &lt;current_date&gt; + 7 days, etc.</pre><p>4. Vertex AI then translates the extracted information into a structured JSON format like this:</p><pre>{<br> &quot;reminder_text&quot;: &quot;take medicine&quot;,<br> &quot;reminder_datetime&quot;: &quot;2024–06–24T18:00:00.000Z&quot;<br>}</pre><p>5. We then send the JSON out to our Cloud SQL MySQL database and store it (we chose to use Prisma in our Node.js app). Here is our Prisma db schema:</p><pre>// This is your Prisma schema file,<br>// learn more about it in the docs: https://pris.ly/d/prisma-schema<br><br>// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?<br>// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init<br><br>generator client {<br>  provider = &quot;prisma-client-js&quot;<br>}<br><br>datasource db {<br>  provider = &quot;mysql&quot;<br>  url      = env(&quot;DATABASE_URL&quot;)<br>}<br><br>model User {<br>  id String @id @default(cuid())<br><br>  // fields<br>  phone     String     @unique<br>  reminders Reminder[]<br><br>  // time<br>  createdAt DateTime @default(now())<br>  updatedAt DateTime @updatedAt<br>}<br><br>model Reminder {<br>  id String @id @default(cuid())<br><br>  // fields<br>  text String<br>  time DateTime<br>  sent Boolean @default(false) // New default value<br>  <br>  // relations<br>  user   User   @relation(fields: [userId], references: [id])<br>  userId String<br><br>  // time<br>  createdAt DateTime @default(now())<br>  updatedAt DateTime @updatedAt<br>}</pre><p>6. Then a background job (we also put it directly in our Node.js app) runs every minute. This job scans the database for reminders scheduled within the next minute.</p><p>7. For identified reminders, the job triggers an SMS notification back to the senior’s phone via the Twilio API precisely at the set reminder_datetime.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2Ff8c3iR9TzCCSDyIAsY%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExZG1pMTE4dHRrOW5qbzBsc290Ymt4ZGxvZnJoa2Izb2tqM2g3dWRxaSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Ff8c3iR9TzCCSDyIAsY%2Fgiphy.gif&amp;image=https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExOW04Z3lkdG5xZmx3cTdibmowcWZseHFrbGdrdTVncDFmMGhsYXVraiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2Ff8c3iR9TzCCSDyIAsY%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="331" frameborder="0" scrolling="no"><a href="https://medium.com/media/7312c1e48ef4464cb378c4c3b8cb754b/href">https://medium.com/media/7312c1e48ef4464cb378c4c3b8cb754b/href</a></iframe><h3>Demo</h3><ul><li>Send your SMS reminder (for example, Remind me to take medicine on 24nd June 18:00) to +44 7822 021078.</li></ul><blockquote>⚡ We bravely topped up our Twilio account.</blockquote><ul><li>You can also fork our solution from here: <a href="https://github.com/richardevcom/memba">richardevcom/memba</a></li><li>Check out our landing page (yeah, because why not?): <a href="https://memba.my.canva.site/">‘memba? website</a></li><li>Here is a quick demo video:</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FlqeuZKZNwKk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DlqeuZKZNwKk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FlqeuZKZNwKk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/fa4f7ab1229aa34045f82391ed56589d/href">https://medium.com/media/fa4f7ab1229aa34045f82391ed56589d/href</a></iframe><h3>🐞 Known issues</h3><ul><li>Our LLM is smart enough to parse data, but not smart enough to determine correct timezone from phone nr. country code. Expect your reminders to have date/time offset.</li></ul><blockquote>⚡ Many countries have multiple timezones (did you know that France has 12 timezones? 🤯) and one phone number country code — which basically leads our solution to have date/time offsets.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*woshdVliXjWu5YFuxEB97w.png" /></figure><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=466ab033bfa5" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/memba-twilio-vertex-ai-reminder-system-for-seniors-who-dont-memba-466ab033bfa5">‘memba?: Twilio &amp; Vertex AI SMS Reminder System for Seniors who don’t ‘memba.</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Destructuring Vue.js props: The Reactivity Challenge]]></title>
            <link>https://blog.stackademic.com/destructuring-vue-props-d146e2bc9c9e?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/d146e2bc9c9e</guid>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[props]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[vuejs]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Mon, 25 Mar 2024 18:05:18 GMT</pubDate>
            <atom:updated>2025-03-22T01:10:10.340Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RxDnh0Slz26SoCJWifcEKQ.jpeg" /></figure><h3><strong>Destructuring Vue.js Props: The Reactivity Challenge</strong></h3><p><em>Destructuring objects is a common practice in JavaScript, and it can make your code cleaner by extracting specific properties. In Vue.js, however, destructuring props can unintentionally break reactivity.</em></p><h3>The Pitfalls of Destructured Props</h3><blockquote>⚡ You cannot destructure <em>defineProps</em> because the resulting destructured variables are not reactive and will not update. <a href="https://vuejs.org/guide/extras/reactivity-transform#reactive-props-destructure">Read more</a></blockquote><p>When you destructure props in Vue.js, the resulting variables become plain JavaScript objects. These objects are not reactive, meaning changes to the original props won’t trigger a re-render in your component. This can lead to stale data being displayed.</p><p>For example, imagine a component displaying a firstName and a lastName based on a prop. If the prop is destructured, the properties won’t update when the parent component changes the prop value.</p><pre>&lt;template&gt;<br>  &lt;div&gt;<br>    &lt;p&gt;Name: {{ firstname }} {{ lastname }}&lt;/p&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import { ref } from &#39;vue&#39;;<br><br>// ❌ Non-reactive props - this won&#39;t re-render<br>const { firstName, lastName } = defineProps({<br>  firstName: String,<br>  lastName: String,<br>});<br>&lt;/script&gt;</pre><h3>Keeping destructured props reactive (if necessary)</h3><p>If you still really, really want to destructure props, there are two ways to maintain reactivity:</p><ul><li><strong>Reference your props object directly using </strong><strong>toRefs()</strong></li></ul><p>Vue’s toRefs function takes a reactive object (like props) and returns a new object where each property is wrapped in a ref. Accessing properties using .value (e.g., prop1.value) ensures reactivity.</p><blockquote>⚡ Vue.js documentation advises against this approach for props, suggesting using the dot notation instead. <a href="https://vuejs.org/api/composition-api-setup.html#accessing-props">Read more</a></blockquote><pre>&lt;template&gt;<br>  &lt;div&gt;<br>    &lt;p&gt;Name: {{ firstName }} {{ lastName }}&lt;/p&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import { ref, toRefs } from &#39;vue&#39;;<br><br>const props = defineProps({<br>  firstName: String,<br>  lastName: String,<br>})<br><br>const { firstName, lastName } = toRefs(props)<br>&lt;/script&gt;</pre><ul><li><strong>Reference your props object directly using </strong><strong>computed():</strong></li></ul><pre>&lt;template&gt;<br>  &lt;div&gt;<br>    &lt;p&gt;{{ fullName }}&lt;/p&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import { computed } from &#39;vue&#39;;<br>const { firstName, lastName } = defineProps({<br>  firstName: String,<br>  lastName: String<br>})<br>const fullName = computed(() =&gt; firstName + lastName)<br>&lt;/script&gt;</pre><h3>The Bottom Line: Embrace the Dot Notation!</h3><p>While destructuring can be useful for non-reactive objects, it’s generally recommended to access props directly using props.propertyName in Vue.js.</p><pre>&lt;template&gt;<br>  &lt;div&gt;<br>    &lt;p&gt;Name: {{ props.firstname }} {{ props.lastname }}&lt;/p&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import { ref } from &#39;vue&#39;;<br><br>// ✅ Reactive props - this will re-render<br>const props = defineProps({<br>  firstName: String,<br>  lastName: String,<br>});<br>&lt;/script&gt;</pre><p>This approach guarantees reactivity and avoids potential issues.</p><p><em>Remember, young traveler, maintaining reactivity is crucial for dynamic Vue components. Choose the approach that suits your style, but be mindful of the potential pitfalls of destructuring props.</em></p><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><h3>Stackademic 🎓</h3><p>Thank you for reading until the end. Before you go:</p><ul><li>Please consider <strong>clapping</strong> and <strong>following</strong> the writer! 👏</li><li>Follow us <a href="https://twitter.com/stackademichq"><strong>X</strong></a><strong> | </strong><a href="https://www.linkedin.com/company/stackademic"><strong>LinkedIn</strong></a><strong> | </strong><a href="https://www.youtube.com/c/stackademic"><strong>YouTube</strong></a><strong> | </strong><a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Discord</strong></a></li><li>Visit our other platforms: <a href="https://plainenglish.io"><strong>In Plain English</strong></a><strong> | </strong><a href="https://cofeed.app/"><strong>CoFeed</strong></a><strong> | </strong><a href="https://venturemagazine.net/"><strong>Venture</strong></a><strong> | </strong><a href="https://blog.cubed.run"><strong>Cubed</strong></a></li><li>More content at <a href="https://stackademic.com"><strong>Stackademic.com</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d146e2bc9c9e" width="1" height="1" alt=""><hr><p><a href="https://blog.stackademic.com/destructuring-vue-props-d146e2bc9c9e">Destructuring Vue.js props: The Reactivity Challenge</a> was originally published in <a href="https://blog.stackademic.com">Stackademic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Less is better — the pitfalls of Tech Bloat]]></title>
            <link>https://medium.com/front-end-weekly/less-is-better-the-pitfalls-of-tech-bloat-907f598c79f2?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/907f598c79f2</guid>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Thu, 12 Oct 2023 08:54:28 GMT</pubDate>
            <atom:updated>2025-03-22T01:10:35.523Z</atom:updated>
            <content:encoded><![CDATA[<h3>Less is better — the pitfalls of Tech Bloat</h3><blockquote><em>⚡️Tech bloat is a term used to describe the excessive use of technologies in a software project.</em></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gOZInTNVip344u5rgHUUCw.jpeg" /></figure><p><em>It’s tempting to believe that piling on more technologies is the path to success. However, the reality paints a different picture. Your tech stack, resembling an Eiffel tower, might seem impressive, but beneath the surface, it’s a breeding ground for complications and inefficiencies.</em></p><h3>Problem — Tech Bloat trend</h3><p>The prevailing trend in the programming world is to stack layers of packages for even the most basic tasks. Simple components like alert modals and forms, once straightforward, are now bundled into massive packages. However, the hidden cost surfaces in the form of bloated code, longer debugging cycles, and an increasingly steep learning curve for newcomers.</p><h4>Consequences of Tech Bloat</h4><ul><li><strong>Longer Development Time:</strong> bloated packages mean longer development cycles, with debugging becoming a labyrinthine challenge.</li><li><strong>Increased Complexity:</strong> each added layer brings complexity, making it difficult to understand the dependencies and slowing down your team.</li><li><strong>Barriers to Collaboration: </strong>Overly complex tech stacks create barriers, hindering collaboration and making it hard for new team members to contribute meaningfully.</li><li><strong>Simple Problems, Complex Solutions:</strong> Not every problem needs a complicated solution. Often, simplicity leads to elegant and efficient outcomes.</li></ul><h3>Solution — a call for simplicity</h3><p>So, here’s my very subjective advice: <strong>deflate your tech stack</strong>. Resist the allure of excess languages, frameworks, or libraries. Embrace simplicity and efficiency. Remember, the goal isn’t to showcase your mastery of complex technologies but to create seamless, innovative solutions. By keeping your stack lean, you not only ease your burden but also foster a collaborative environment where ideas flow freely, unburdened by needless complexities.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fupscri.be%2Ff%2F788447%3Fas_embed%3Dtrue&amp;dntp=1&amp;display_name=Upscribe&amp;url=https%3A%2F%2Fupscri.be%2Ff%2F788447&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=upscri" width="800" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/9cd2278f71c50d8129842e5a3487e25f/href">https://medium.com/media/9cd2278f71c50d8129842e5a3487e25f/href</a></iframe><h4>Tips for Deflating Your Tech Stack</h4><ul><li><strong>Choose the right tools.</strong> Select tools that fit the task at hand. Avoid complex frameworks for simple tasks.</li><li><strong>Use focused libraries.</strong> Instead of using a general-purpose library, use a library that is specifically designed for the task you need to do.</li><li><strong>Keep dependencies updated.</strong> Outdated dependencies can lead to security vulnerabilities and compatibility issues.</li><li><strong>Remove unused dependencies.</strong> If you’re not using a dependency, remove it. This will reduce the size and complexity of your project.</li></ul><h3>Conclusion</h3><blockquote>⚡️Remember, <strong>the goal is to create a solution, not a complicated mess.</strong></blockquote><p>By deflating your tech stack, you can write simpler, more efficient code that is easier to maintain and extend.</p><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=907f598c79f2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/front-end-weekly/less-is-better-the-pitfalls-of-tech-bloat-907f598c79f2">Less is better — the pitfalls of Tech Bloat</a> was originally published in <a href="https://medium.com/front-end-weekly">Frontend Weekly</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom API server with basic CRUD — Apollo, GraphQL & MongoDB]]></title>
            <link>https://towardsdev.com/custom-api-server-with-basic-crud-apollo-graphql-mongodb-8c107523a09a?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8c107523a09a</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[apollo]]></category>
            <category><![CDATA[mongodb]]></category>
            <category><![CDATA[api]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Mon, 16 Jan 2023 06:05:58 GMT</pubDate>
            <atom:updated>2025-03-22T01:12:05.640Z</atom:updated>
            <content:encoded><![CDATA[<h3>Custom API server with basic CRUD — JS, Apollo, GraphQL &amp; MongoDB</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*9rWRV2XiIFxAz8Xwq5f-ag.png" /><figcaption>API CRUD — GraphQL, Apollo, Mongodb</figcaption></figure><p><em>[…] All that was left to do for my cross-platform hybrid web app was to test it with actual content and create CRUD. First thought, obviously, was to write middleware for back-end that will communicate with database. However, I had planned on adding more features over time, so doing that on multiple platforms would be overkill. Therefore, I quickly decided on building API server, that could... well... do it all.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/735/1*qVibWDBXjSwIcBjO8C8zJA.png" /></figure><p>Most commonly used data format for transferring data to a client is <em>JSON</em> and for controlling it, well.. used to be <em>REST (don’t throw rocks at me just yet </em>😅<em>). </em>If you gradually increase your app’s complexity as well as database, it can and will be very resource heavy solution in long term. Luckily for us, there are 2 much better alternatives — <em>GraphQL</em> and <em>gRPC</em>. Even better — there is also <em>Node.js</em> friendly <em>Apollo Server</em> (<em>GraphQL</em><strong><em> </em></strong><em>server</em>).</p><p>So, in this article “slash” tutorial, I’ll try to dig into creating custom <em>API Apollo Server</em> and as a bonus, we’ll write some basic <em>CRUD </em>for it.</p><p>Let’s dig in! 👏</p><h3>Before you jump in</h3><p>It goes without saying, that you’ll need basic knowledge in <em>Node.js</em>, <em>NPM</em> and how to use its command-line tool. At the moment of writing this tutorial, I had following versions set-up:</p><pre>node -v<br>v16.16.0<br>npm -v<br>8.19.2</pre><p>What about the database? I personally prefer going with good old <em>MongoDB</em>. If you have avoided it until now, <a href="https://www.mongodb.com/docs/drivers/node/current/"><em>get to know it better here</em></a>, it’s really intuitive and fast. Oh, and we’ll also use it the <em>OOP</em> way<strong>, </strong>so meet <em>Mongoose </em><strong>—</strong> it will be our “<em>driver</em>” for <em>MongoDB</em>.</p><p>⚡<em>We’ll be running our MongoDB server on Docker. Read —</em><strong><em> </em></strong><a href="https://www.linode.com/docs/guides/set-up-mongodb-on-docker/#before-you-begin"><em>how to install MongoDB on Docker</em></a><em>. Alternatively you can use </em><a href="https://www.mongodb.com/cloud/atlas/register"><em>MongoDB Atlas</em></a><em> (which is remote &amp; ready solution).</em></p><p>I installed and initialized MongoDB within Docker like so (<em>replace </em><em>mongoadmin and </em><em>mongopasswd to whatever you want</em>):</p><pre>docker pull mongo<br>docker run -d  --name mongodb  -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=mongopasswd mongo</pre><p>Lastly, instead of writing our <em>API</em><strong> </strong>core ourselves, we’ll be using the star of this episode —<strong> </strong><em>Apollo Server</em><strong> </strong>(a.k.a. <em>GraphQL server</em>). It has detailed documentation available <a href="https://www.apollographql.com/docs/apollo-server/">here</a>.</p><h3>Initialization</h3><p>Let’s dig in and start by creating our project folder:</p><pre>mkdir mag-api-server<br>cd mag-api-server</pre><p><em>We’ll go with </em><strong><em>MAG</em></strong><em> as in </em><strong><em>M</em></strong><em>ongoDB, </em><strong><em>A</em></strong><em>pollo and </em><strong><em>G</em></strong><em>raphQL for the sake of... me loving abbreviations. </em>🤷‍♂️</p><p>Then initialize our <strong>Node.js</strong> project:</p><pre>npm init -y</pre><p>Let’s update our package.json by setting type to module (<em>so we can load our JS as ES modules</em>) and changing npm test command to npm start:</p><pre>...<br>&quot;type&quot;: &quot;module&quot;,<br>&quot;scripts&quot;: {<br>   &quot;start&quot;: &quot;node index.js&quot;<br>},<br>...</pre><h3>Apollo Server setup</h3><p>Our project directory is set up, so let’s install <em>Apollo Server</em> with <em>GraphQL</em>:</p><pre>npm install @apollo/server graphql -S</pre><p>Then create an index.js file in your project root folder and import packages from above in it.</p><pre>import { ApolloServer } from &quot;@apollo/server&quot;<br>import { startStandaloneServer } from &quot;@apollo/server/standalone&quot;</pre><p>Define temporary movie <em>GraphQL</em> schema for testing purposes below:</p><pre>...<br>const typeDefs = `#graphql<br>  type Movie {<br>    title: String<br>    director: String<br>  }<br><br>  type Query {<br>    movies: [Movie]<br>  }<br>`</pre><p>And add movies data set below:</p><pre>...<br>const movies = [<br>   {<br>      title: &quot;Edward Scissorhands&quot;,<br>      director: &quot;Tim Burton&quot;,<br>   },<br>   {<br>      title: &quot;The Terrifier 2&quot;,<br>      director: &quot;Damien Leone&quot;,<br>   },<br>]</pre><p>Next, we’ll need to define a resolver.</p><blockquote>⚡”Resolver tells Apollo Server how to fetch the data associated with a particular type.” Because our movies array is hard-coded, the corresponding resolver is straightforward.</blockquote><pre>...<br>const resolvers = {<br>   Query: {<br>      movies: () =&gt; movies,<br>   },<br>}</pre><p>Let’s define our <em>Apollo Server</em><strong> </strong>instance:</p><pre>const server = new ApolloServer({<br>   typeDefs,<br>   resolvers,<br>})<br><br>const { url } = await startStandaloneServer(server, {<br>   listen: { port: 4000 },<br>})<br><br>console.log(`🚀  Server ready at: ${url}`)</pre><p>And test run our server:</p><pre>npm start</pre><p>Which should print back:</p><pre>&gt; mage-api-server@1.0.0 start<br>&gt; node index.js<br>🚀  Server ready at: http://localhost:4000/</pre><p>If you open <a href="http://localhost:4000/">http://localhost:4000/</a>, you will see sandbox environment, where we will be executing <em>GraphQL</em> queries.</p><p>Go ahead and run this query in operations tab:</p><pre>query Movies {<br>   movies {<br>      title<br>      director<br>   }<br>}</pre><p>If everything is set-up correctly, you will receive this <strong>JSON</strong> response:</p><pre>{<br>   &quot;data&quot;: {<br>      &quot;movies&quot;: [<br>         {<br>            &quot;title&quot;: &quot;Edward Scissorhands&quot;,<br>            &quot;director&quot;: &quot;Tim Burton&quot;<br>         },<br>         {<br>            &quot;title&quot;: &quot;The Terrifier 2&quot;,<br>            &quot;director&quot;: &quot;Damien Leone&quot;<br>         }<br>      ]<br>   }<br>}</pre><h3>Restructure Schemas &amp; resolvers</h3><p>With our <em>Apollo Server</em> running and querying, let’s restructure our project files and create more automated type schema inclusion. We’ll start by creating ./schemas/ folder which will hold our <em>GraphQL</em> type schemas.</p><p>For resolvers — create ./resolvers/ folder as well as ./resolvers/movie.js file. Next, cut our movies constant data set and resolvers definitions from index.js and paste them into ./resolvers/movie.js. Finally, prefix resolvers with export and rename it to moviesResolvers:</p><pre>const movies = [<br>   {<br>      title: &quot;Edward Scissorhands&quot;,<br>      director: &quot;Tim Burton&quot;,<br>   },<br>   {<br>      title: &quot;The Terrifier 2&quot;,<br>      director: &quot;Damien Leone&quot;,<br>   },<br>]<br><br>export const moviesResolvers = {<br>   Query: {<br>      movies: () =&gt; movies,<br>   },<br>}</pre><p>Next, create ./schemas/Movie.graphql file, cut Movie type schema from ./index.js and paste it in our newly made file (without <em>GraphQL</em> syntax and<em> JS</em> definition):</p><pre>type Movie {<br>   title: String<br>   director: String<br>}<br><br>type Query {<br>   movies: [Movie]<br>}</pre><p>Before we create loader for <em>resolvers </em>and <em>schemas</em>, let’s install necessary <em>GraphQL Tools</em>:</p><pre>npm i @graphql-tools/load @graphql-tools/schema @graphql-tools/graphql-file-loader -S</pre><p>Now create ./loader.js in project root folder and import both scheme and resolvers using our new tools:</p><pre>import { loadSchema } from &quot;@graphql-tools/load&quot;<br>import { GraphQLFileLoader } from &quot;@graphql-tools/graphql-file-loader&quot;<br>import { moviesResolvers } from &quot;./resolvers/movie.js&quot;<br><br>export const typeDefs = await loadSchema(&quot;./schemas/**/*.graphql&quot;, { loaders: [new GraphQLFileLoader()] })<br>export const resolvers = [moviesResolvers]</pre><blockquote>⚡Using loaders in the manner above will not only automize module, resolver and schema import, but also make code more readable and writable in long term.</blockquote><p>Let’s go back to ./index.js and import schemas and resolvers:</p><pre>...<br>import { resolvers, typeDefs } from &quot;./loader.js&quot;<br>...</pre><p>To test if everything works fine, re-run the server npm start command and query movies in <a href="http://localhost:4000/">http://localhost:4000/</a>.</p><h3>MongoDB via Mongoose</h3><p>We got our type query-able <em>schema </em>and <em>resolvers.</em> Let’s remove hard-coded data set in ./resolvers/movie.js and connect database instead.</p><p>Adding <em>MongoDB</em> should be pretty intuitive, however thinking ahead we’d probably want our data the <em>OOP</em><strong> </strong>way, right? This is where <em>Mongoose</em> comes in. It’s a great “<em>driver</em>” for that.</p><p>As metnioned in “<strong>Before you jump in</strong>” section, we will be using <em>Dockerized</em> <em>MongoDB </em>approach. To test if our <em>MongoDB</em> container is running, let’s run this command:</p><pre>docker ps -a</pre><p>If you see your container and it’s status indicates it’s running, we’re ready to continue.</p><p>So let’s continue by installing <em>Mongoose</em>:</p><pre>npm i mongoose -S</pre><p>Then import it in our ./index.js:</p><pre>import mongoose from &quot;mongoose&quot;</pre><p>Now, initialize our <em>MongoDB</em><strong> </strong>connection somewhere above our server constant (<em>replace </em><em>mongoadmin and </em><em>mongopasswd to whatever you provided when initializing Docker container</em>):</p><pre>...<br>mongoose.Promise = global.Promise<br>mongoose.set(&quot;strictQuery&quot;, false)<br>mongoose.connect(&quot;mongodb://mongoadmin:mongopasswd@localhost:27017/?authSource=admin&quot;)<br>......</pre><p>So, we have our connection to <em>MongoDB</em>, but we still need to replace hard-coded data set with a model.</p><p>Create ./models folder and create ./models/movie.js. Open it up and create movie database schema:</p><pre>import mongoose from &quot;mongoose&quot;<br><br>const Schema = mongoose.Schema<br>const MovieSchema = new Schema(<br>   {<br>      title: {<br>         type: String,<br>         default: &quot;&quot;,<br>         required: true,<br>      },<br>      director: {<br>         type: String,<br>         default: &quot;&quot;,<br>         required: true,<br>      },<br>   },<br>   {<br>      timestamps: {<br>         createdAt: &quot;created_at&quot;,<br>         updatedAt: &quot;updated_at&quot;,<br>      },<br>   }<br>)<br><br>export default mongoose.model(&quot;movie&quot;, MovieSchema)</pre><p>In theory — this is it. However, I mentioned something about <em>CRUD </em>before, so let’s dig into that and customize our <em>MAG API</em><strong> </strong>a little bit more. 😅</p><h3>CRUD</h3><p>Because I love writing so much, I want us to create simple <em>CRUD</em> logic in our only resolver ./resolvers/movie.js. If you haven’t already, remove hard-coded dummy data set and import ./models/movie.js.</p><pre>import Movie from &quot;../models/movie.js&quot;<br>...</pre><p>Also, let’s redefine our movies query in ./resolvers/movie.js :</p><pre>...<br>export const moviesResolvers = {<br>  Query: {<br>    async movies(root, {}, ctx) {<br>      return await Movie.find()<br>    },<br>  },<br>}<br>...</pre><p>Lastly, before we continue — it is always smart to use some kind of unique identifiers for your records, therefore let’s go ahead and use <em>MongoDB</em> default one _id and reuse it in our movie schema ./schemas/Movie.graphql:</p><pre>type Movie {<br>  _id: ID!<br>  title: String<br>  director: String<br>}<br>...</pre><h3>Create (CRUD)</h3><p>Define <em>CREATE</em> mutation in our ./schemas/movie.graphql type schema:</p><pre>...<br>type Mutation {<br>   addMovie(title: String!, director: String!): Movie!<br>}</pre><p>Next, <em>CREATE</em> mutation resolver below in ./resolvers/movie.js:</p><pre>...<br>export const moviesResolvers = {<br>  ...<br>  Mutation: {<br>     async addMovie(root, { title, director }, ctx) {<br>        return await Movie.create({<br>           title,<br>           director,<br>        })<br>     },<br>  }<br>}</pre><p>Now, let’s restart our server and test if we can add new movie via sandbox at <a href="http://localhost:4000/">http://localhost:4000/</a> by querying this mutation:</p><pre>mutation Mutation($director: String!, $title: String!) {<br>  addMovie(director: $director, title: $title) {<br>    _id<br>    title<br>    director<br>  }<br>}</pre><p>And for variables let’s enter data from previously deleted data set. Write couple different ones for diversity of things.</p><pre>{<br>  &quot;title&quot;: &quot;Prometheus&quot;,<br>  &quot;director&quot;: &quot;Ridley Scott&quot;<br>}</pre><p>When you’re ready — run mutation query.</p><p>To confirm that we actually saved the movie, open up new query tab in sandbox and re-run:</p><pre>query Query {<br>  movies {<br>    _id<br>    title<br>    director<br>  }<br>}</pre><p>You will receive your newly created movie within <em>JSON</em><strong> </strong>response.</p><p>Now, we have to mirror our steps above and create read, update and delete logic. <em>There is no CRUD without RUD. </em>🥁</p><h3>Read (CRUD)</h3><p>We already have a method to <em>READ</em> all movies, so let’s just define <em>READ</em> query in our ./schemas/movie.graphql type schema for reading a single movie (using its ID):</p><pre>...<br>type Query {<br>  ...<br>  getMovie(_id: ID!): Movie<br>}</pre><p>Define READ query resolver below in ./resolvers/movie.js:</p><pre>...<br>Query: {<br>  ...<br>  async getMovie(root, { _id }, ctx) {<br>    return await Movie.findOne({ _id })<br>  }<br>}<br>...</pre><p>Restart the server and test if you can get a movie by its _id via sandbox at <a href="http://localhost:4000/">http://localhost:4000/</a> using this query:</p><pre>query Query($id: ID!) {<br>  getMovie(_id: $id) {<br>    _id<br>    director<br>    title<br>  }<br>}</pre><p>And use your newly added movie’s _id as a variable value:</p><pre>{<br>  &quot;id&quot;: &quot;63c224272a9dd1ef31d73de5&quot;<br>}</pre><h3>Update (CRUD)</h3><p>Define <em>UPDATE</em> mutation in our ./schemas/movie.graphql type schema:</p><pre>...<br>type Mutation {<br>  ...<br>  updateMovie(_id: ID!, title: String, director: String): Movie<br>}</pre><p>Now, to update movie, we’ll <em>UPDATE</em> mutation in out ./resolvers/movie.js like so:</p><pre>...<br>Mutation: {<br>  ...<br>  async updateMovie(root, { _id, title, director }, ctx) {<br>    return await Movie.findOneAndUpdate({ _id }, { title, director })<br>  },<br>}</pre><p>Restart the server, define query and provide _id and one or both variables for it to update:</p><pre>mutation Mutation($id: ID!, $title: String, $director: String) {<br>  updateMovie(_id: $id, title: $title, director: $director) {<br>    director<br>    title<br>  }<br>}</pre><p>I’m updating “<em>Prometheus</em>” movie title and director, so I’m providing its _id value from previous query response and the rest below it:</p><pre>{<br>  &quot;id&quot;: &quot;63c224272a9dd1ef31d73de5&quot;,<br>  &quot;title&quot;: &quot;Antichrist&quot;,<br>  &quot;director&quot;: &quot;Lars von Trier&quot;<br>}</pre><h3>Delete (CRUD)</h3><p>Time flies and taste in movies changes, so we will obviously need a way to delete a movie in future, therefore let’s go and define <em>DELETE </em>mutation in our ./schemas/movie.graphql type schema:</p><pre>...<br>type Mutation {<br> ...<br> deleteMovie(_id: ID!): Movie<br>}</pre><p>Next, define <em>DELETE </em>mutation resolver below in ./resolvers/movie.js:</p><pre>Mutation: {<br>  ...<br>  async deleteMovie(root, { _id }, ctx) {<br>    return await Movie.findOneAndDelete({ _id })<br>  },<br> },</pre><p>Finally, let’s go ahead, restart and test with this query:</p><pre>mutation Mutation($id: ID!) {<br>  deleteMovie(_id: $id) {<br>    _id<br>    director<br>    title<br>  }<br>}</pre><p>Now let’s define _id of movie that we’ll be deleting:</p><pre>{<br>  &quot;id&quot;: &quot;63c224272a9dd1ef31d73de5&quot;<br>}</pre><p>Finally, restart the server and query all movies to see if it all worked!</p><h3>🎉Congratulations!</h3><p>You did it! You read this lengthy “<em>how-to</em>” tutorial and created your own <em>API</em> server with basic <em>CRUD</em>. 👏</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FjJQC2puVZpTMO4vUs0%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FjJQC2puVZpTMO4vUs0%2Fgiphy.gif&amp;image=https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExbGxzdmI4NmVlNzc2Y3FycjV1aWc5dzl1MTk3b3hnNmhzOGw2NXhxOCZlcD12MV9naWZzX2dpZklkJmN0PWc%2FjJQC2puVZpTMO4vUs0%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="363" frameborder="0" scrolling="no"><a href="https://medium.com/media/ef374563fef9a206de62b0bf5c507b69/href">https://medium.com/media/ef374563fef9a206de62b0bf5c507b69/href</a></iframe><p><em>Leave a comment below if and where you had trouble running the server, or if you simply want to make a suggestion.</em></p><h3>What’s next?</h3><p>This <em>API</em><strong> </strong>server is bare bone by itself, so your journey doesn’t end here. “<em>One simply does not CRUD without an auth”.</em> Surely you wouldn’t want to lose all your precious <em>po..</em> I mean <em>movies</em>!? 👀</p><p>So, here are some ideas what to do next:</p><ul><li>What is <em>API</em> without a key? Try integrating <a href="https://www.npmjs.com/package/jsonwebtoken"><strong>JWT</strong></a> with basic key based permission logic;</li><li>Integrate this with front-end framework, for example, <a href="https://vuejs.org/guide/introduction.html"><strong>Vue.js</strong></a>;</li><li><em>Containerize</em> your <em>API </em>server and <em>MongoDB</em> for<em> Docker</em> (<a href="https://www.docker.com/blog/9-tips-for-containerizing-your-node-js-application/"><em>make easily deployable container</em></a>);</li><li>Write sanitizers &amp; conditions to work with duplicates or queries returning errors on non-existing records;</li><li>Secure queries and mutations by writing checks for inputs and customizing callbacks (<em>I might do part 2 regarding this</em>);</li></ul><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8c107523a09a" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/custom-api-server-with-basic-crud-apollo-graphql-mongodb-8c107523a09a">Custom API server with basic CRUD — Apollo, GraphQL &amp; MongoDB</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Publish your first NPM package — next step towards open-source]]></title>
            <link>https://towardsdev.com/publish-your-first-npm-package-c9f41a51c6f0?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/c9f41a51c6f0</guid>
            <category><![CDATA[package]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[npm]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Sun, 14 Aug 2022 19:35:36 GMT</pubDate>
            <atom:updated>2025-03-22T01:12:22.346Z</atom:updated>
            <content:encoded><![CDATA[<h3>Publish your first NPM package — next step towards open-source</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qHHHaXCHxopZgH1-xof15A.png" /></figure><p><em>A while back I wrote an article about </em><a href="https://medium.com/js-dojo/custom-vue3-boilerplate-9635806acde3"><em>custom Vue 3 boilerplate</em></a><em> where I swiftly went over how to stack your own reusable boilerplate. With surprisingly positive feedback, it became obvious that I’ll open-source it. So, while hosting it on </em><a href="https://github.com/richardevcom/vue3-boilerplate"><strong><em>GitHub</em></strong></a><em>, I decided to also publish my boilerplate on </em><a href="https://www.npmjs.com/package/@richardev/vue3-boilerplate"><strong><em>NPM </em></strong></a><em>— </em><strong><em>N</em></strong><em>ode </em><strong><em>P</em></strong><em>ackage </em><strong><em>M</em></strong><em>anager — to make easily manageable and quickly install-able package.</em></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FR9YDaIKekP9HcTah5r%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FR9YDaIKekP9HcTah5r%2Fgiphy-downsized.gif&amp;image=https%3A%2F%2Fi.giphy.com%2Fmedia%2FR9YDaIKekP9HcTah5r%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="244" frameborder="0" scrolling="no"><a href="https://medium.com/media/29954e788df66af6cb427af6c613d68e/href">https://medium.com/media/29954e788df66af6cb427af6c613d68e/href</a></iframe><p>With <strong>Node.js</strong> becoming more and more popular among programmers, correlatively numerous popular modules were being re-published and maintained on <strong>NPM </strong>“registry”. I too wanted to give back to open-source community, therefore in this article/tutorial I will go through the process of publishing my custom boilerplate on <strong>NPM </strong>as an example.</p><h3>Prerequisite</h3><p>One of the first things you’ll need are <strong>verified NPM account</strong> and <strong>installed Node.js</strong>. Obviously. 😅</p><h3>Create NPM account</h3><p>Go ahead, visit <a href="https://www.npmjs.com/signup"><strong>NPM </strong><em>Signup page</em></a>, fill out the sign up form and verify your account with one time password (<em>sent to your e-mail</em>).</p><p>⚡<em>Ideally, setup </em><strong><em>2-Factor Authentification </em></strong><em>protection before you continue.</em></p><h3>Install Node.js</h3><p>Now it’s time to install <strong>Node.js</strong> (<em>it comes together with </em><strong><em>NPM </em></strong><em>command-line tool</em>) — visit <a href="https://nodejs.org/en/download/">https://nodejs.org/en/download/</a> and select installation depending on your OS and CPU cores of the machine.</p><p>⚡<em>Note — if you have 2 or more CPU cores, you should use 64bit installer.</em></p><p>Run the installer. You’ll be fine by going with default settings, just make sure you’ve selected “<em>Add to path</em>” — this will allow you to run <strong>NPM command-line</strong>.</p><p>After installation is done — open up terminal and test if you can use <strong>Node.js</strong> &amp; <strong>NPM </strong>command-line tool:</p><pre>node -v // Node.js Version<br>npm -v  // NPM version</pre><p>👏 It works!</p><h3>Initialize our package</h3><p>Great, now we can initialize our package of choice. In my case, I’ll open up my <a href="https://medium.com/js-dojo/custom-vue3-boilerplate-9635806acde3"><strong>Vue 3 boilerplate</strong></a> directory and start <strong>NPM </strong>initialization process:</p><pre>cd vue3-boilerplate<br>npm init</pre><p>⚡<em>Feel free to use my </em><a href="https://medium.com/js-dojo/custom-vue3-boilerplate-9635806acde3"><strong><em>Vue 3 boilerplate</em></strong></a><em> for the sake of this tutorial.</em></p><p>You’ll now be asked to fill in the basic package information, such as — name, version, description, keywordsand etc. Once done, new package.json file will be created in your project’s root directory, containing all the package information.</p><p>⚡<em>Remember — make your package more reachable… add as detailed information as possible, as well as many keyword combinations as possible. You can learn more about configuring </em><em>package.json </em><a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-json"><em>here</em></a><em>.</em></p><h3>Login</h3><p>Before we can publish, we’ll have to login to <strong>NPM</strong>:</p><pre><strong>npm login</strong><br>Username: <em>&lt;your_username&gt;</em><br>Password: <em>&lt;your_password&gt;</em><br>Email: (this IS public) <em>&lt;your_e-mail&gt;</em></pre><p>If you don’t have <strong>2FA </strong>setup (<em>which I strongly advice you to do</em>), you’ll be sent an <strong><em>O</em></strong><em>ne-</em><strong><em>T</em></strong><em>ime </em><strong><em>P</em></strong><em>assword</em> to your e-mail. Copy it and finish your login process:</p><pre>npm notice Please check your email for a one-time password (OTP)<br>Enter one-time password: <em>12345678</em><br>Logged in as <em>&lt;your_username&gt;</em> on <a href="https://registry.npmjs.org/">https://registry.npmjs.org/</a>.</pre><h3>Final test</h3><p><em>There are many </em><em>npm link tutorials out there on how to test a package before publishing, so in contrary I’ll do the other, much simpler way.</em></p><p>Create ./test directory in ./vue3-boilerplate project’s root directory, go into ./test directory and initialize test install in the folder:</p><pre>mkdir test<br>cd test<br>npm i &quot;../&quot;<br>// or<br>npm i &quot;absolute/path/to/&lt;your_package_name&gt;&quot;</pre><p>⚡<em>Always test before publishing!</em></p><h3>Publish package</h3><p>Now — 🥁<em>(drumroll)</em> the moment of truth — time to publish our solution to <strong>NPM </strong>public repository <em>(remember, you have to be inside your project directory when running this)</em>:</p><pre>npm publish --access public</pre><p>You’ll be asked for either <strong>2FA</strong> code or <strong><em>O</em></strong><em>ne-</em><strong><em>T</em></strong><em>ime </em><strong><em>P</em></strong><em>assword</em> sent to your e-mail. Enter one of them and finish the publishing process.</p><p>If everything was entered correctly, you should see this in the end:</p><pre>npm notice Publishing to <a href="https://registry.npmjs.org/">https://registry.npmjs.org/</a><br>+ vue3-boilerplate@1.0.6</pre><p>⚡<em>To publish privately, remove </em><em>--access public parameter. Remember, you have to have at least Pro subscription to have access to private package hosting on </em><strong><em>NPM</em></strong><em>.</em></p><h3>Updating package</h3><p>In order to update your package, you’ll have to update the version number as well. For testing purposes, let’s open up our package.json file and increase the version number from 1.0.0 to 1.0.1.</p><pre>...</pre><pre>&quot;version&quot;: &quot;1.0.1&quot;</pre><pre>...</pre><p>Save and re-publish your package again. This time you can use one of the version control commands:</p><pre>npm version patch // 1.0.1 =&gt; 1.0.2<br>npm version minor // 1.1.0 =&gt; 1.2.0<br>npm version major // 1.0.0 =&gt; 2.0.0</pre><p>Then:</p><pre>npm publish --access public</pre><p>Now, if you visit your package’s <strong>NPM </strong>repository page, you should see the newly updated version.</p><h3>🎉Congratulations!</h3><p>You’re ready to open-source your own <strong>NPM </strong>packages. 👏</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2F3o7abKhOpu0NwenH3O%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2F3o7abKhOpu0NwenH3O%2Fgiphy.gif&amp;image=https%3A%2F%2Fi.giphy.com%2Fmedia%2F3o7abKhOpu0NwenH3O%2Fgiphy.gif&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="244" frameborder="0" scrolling="no"><a href="https://medium.com/media/c292d619b812c71d2acf4faa2c08b304/href">https://medium.com/media/c292d619b812c71d2acf4faa2c08b304/href</a></iframe><h3>Final words</h3><p>In conclusion I just want to mention a couple crucial points regarding publishing packages:</p><ul><li><strong>Always </strong>—<strong> and I mean ALWAYS</strong> —<strong> test before publishing.</strong> <em>Nobody wants to install a broken package. There are many different testing tools depending on your stack.</em></li><li><strong>403 </strong>—<em> Forbidden </em>— an error during publishing can mean many things — <em>your newly created account e-mail is not verified, the name of your package is already taken, you don’t have Pro subscription to publish privately or you’re updating the same version.</em></li><li>Write <strong>README.md </strong>file. <em>Imagine — you’re reaching the views, but nobody is downloading your solution because, well.. there is no documentation on how to use your solution. </em>🤨</li><li><strong>NPM </strong>has a lot more great commands up its sleeves — simply hit npm help or npm help &lt;command&gt; to list them all or <a href="https://docs.npmjs.com/cli/v6/commands">read its documentation</a>. <em>I found </em><a href="https://glebbahmutov.com/blog/npm-tips-and-tricks/"><em>this article</em></a><em> with some great </em><strong><em>NPM </em></strong><em>tips &amp; tricks.</em></li><li>Automate your <strong>NPM </strong>package installation with <a href="https://docs.npmjs.com/cli/v8/commands/npx"><strong>NPX</strong></a><strong> </strong>or <a href="https://docs.npmjs.com/cli/v8"><strong>NPM CLI</strong></a>. <em>It is a great idea to give developers the possibility to configure your package during main installation process.</em></li></ul><p><em>If you’re having trouble following my steps above, or simply liked this article — I’ll appreciate it if you reach out in the comments below. 👋</em></p><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c9f41a51c6f0" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/publish-your-first-npm-package-c9f41a51c6f0">Publish your first NPM package — next step towards open-source</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom Vue 3 boilerplate — Vite, Pinia, Vue Router & Tailwind CSS]]></title>
            <link>https://medium.com/js-dojo/custom-vue3-boilerplate-9635806acde3?source=rss-6f5a6c2cf7d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/9635806acde3</guid>
            <category><![CDATA[vuex]]></category>
            <category><![CDATA[vuejs]]></category>
            <category><![CDATA[vue-router]]></category>
            <category><![CDATA[tailwind-css]]></category>
            <category><![CDATA[vitejs]]></category>
            <dc:creator><![CDATA[richardevcom]]></dc:creator>
            <pubDate>Tue, 02 Aug 2022 15:52:10 GMT</pubDate>
            <atom:updated>2025-03-22T01:12:42.530Z</atom:updated>
            <content:encoded><![CDATA[<h3>Custom Vue 3 boilerplate — Vite, Pinia, Vue Router &amp; Tailwind CSS</h3><blockquote>⚡<strong>Update</strong>: I’ve recreated <strong>TypeScript </strong>ready version of this boilerplate <a href="https://github.com/richardevcom/vue-ts-boilerplate"><strong>here</strong></a>.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WCd95skuXhjjeFbOBGyJ4g.png" /></figure><p><em>A while back I realized I’m trashing my beautiful workspace with quite a few repetitive Vue web apps. Shortly before backing them up to never be seen again, it clicked — why not repurpose those reusable parts and simply stack them in a boilerplate? </em>🤔</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FEXvj6EsTB63YI%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FEXvj6EsTB63YI%2Fgiphy.gif&amp;image=https%3A%2F%2Fi.giphy.com%2Fmedia%2FEXvj6EsTB63YI%2F200.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="169" frameborder="0" scrolling="no"><a href="https://medium.com/media/e9c79227c5cb278d01500cea67a29f54/href">https://medium.com/media/e9c79227c5cb278d01500cea67a29f54/href</a></iframe><p>I doubt that many <em>devs </em>like staging same project and repeat same initial commands again and again, therefore in this article “slash” tutorial I will swiftly go over how does one stack and configure a boilerplate.</p><h3>Boilerplate stack</h3><p>The stack of this boilerplate is focused on web app front end, therefore I’ll keep it simple and use following packages:</p><ul><li><a href="https://vitejs.dev/"><strong>Vite</strong></a><strong> </strong>— dev tool for building and serving</li><li><a href="https://pinia.vuejs.org/"><strong>Pinia</strong></a><strong> </strong>— for storing</li><li><a href="https://router.vuejs.org/"><strong>Vue Router</strong></a> — for dynamic routing</li><li><a href="https://tailwindcss.com/"><strong>Tailwind CSS</strong></a> — for looks</li><li><a href="https://github.com/jpkleemans/vite-svg-loader"><strong>Vite SVG loader</strong></a> (<em>optional</em>) — to easily import SVGs</li></ul><h3>Prerequisite</h3><p>Before we start, I’m assuming you’re somewhat acquainted with command line and have Node.js installed. If not — download it <a href="https://nodejs.org/en/download/">here</a>. To check if it’s installed, simply run node -v command.</p><h3>Quickstart — Vue, Router &amp; Store</h3><p>Let’s begin by initializing our boilerplate project.</p><pre>npm init vue@latest</pre><p>Now enter the project name. I’ll be using vue3-boilerplate.On feature prompt choose to install <strong>Pinia </strong>and <strong>Vue Router</strong>.</p><pre>✔ Project name: <strong>vue3-boilerplate</strong><br>...<br>✔ Add Vue Router for Single Page Application development? <strong>Yes</strong><br>✔ Add Pinia for state management? <strong>Yes</strong></pre><p>It’s time to move into our project folder, install packages and run our boilerplate in development environment.</p><pre>cd vue3-boilerplate<br>npm install<br>npm run dev</pre><p>You should be seeing on your local development environment <a href="http://127.0.0.1:5173/">http://127.0.0.1:5173</a> this example page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_H1bU7e9kZmB-830l449Dw.png" /></figure><p><em>You can learn more about building or serving your app in </em><a href="https://vuejs.org/guide/quick-start.html"><em>this guide</em></a><em>. Oh, and read more about </em><strong><em>Pinia </em></strong><a href="https://pinia.vuejs.org/introduction.html#comparison-with-vuex"><em>here</em></a><em>. It is very similar to </em><strong><em>Vuex</em></strong><em>.</em></p><h3>Add Tailwind CSS</h3><p>👏 Great, we got our base, now we need the looks. Following <a href="https://tailwindcss.com/docs/guides/vite">this guide</a> we are installing Tailwind CSS, its dependencies and then initializing configuration file.</p><pre>npm install -D tailwindcss postcss autoprefixer<br>npx tailwindcss init -p</pre><p>In your project root you’ll now find tailwind.config.js file — let’s open it and add our template paths:</p><pre>module.exports = {<br>   content: [<br>      &quot;./index.html&quot;,<br>      &quot;./src/**/*.{vue,js,ts,jsx,tsx}&quot;,<br>   ],<br>   ...<br>}</pre><p>Next we will need to load @tailwind directives, so let’s create tailwind.css in our /src/assets folder with following directives:</p><pre>@tailwind base;<br>@tailwind components;<br>@tailwind utilities;</pre><p>Now let’s load this CSS file by importing it in the very top of /src/assets/main.css file:</p><pre>@import &quot;./tailwind.css&quot;;</pre><p><em>Since we already import </em><em>/src/assets/main.css file in </em><em>/src/main.js, we are good to use Tailwind’s utility classes in our project.</em></p><p>Let’s test it out by adding some classes inside /src/views/AboutView.vue in &lt;h1&gt; tag:</p><pre>&lt;template&gt;</pre><pre>   &lt;div class=&quot;about&quot;&gt;</pre><pre>      &lt;h1 class=&quot;text-xl font-medium text-white&quot;&gt;This is an about page&lt;/h1&gt;</pre><pre>   &lt;/div&gt;</pre><pre>&lt;/template&gt;</pre><p><em>We can also define our CSS properties separately below. To do so, we will need to install this </em><strong><em>SASS </em></strong><em>package and</em><strong><em> PostCSS</em></strong><em> plugin — </em><em>sass &amp; </em><em>postcss-import:</em></p><pre>npm install -D sass postcss-import</pre><p>Now let’s <em>use </em>@apply<em> with our utility classes:</em></p><pre>&lt;template&gt;<br>   ...</pre><pre>   &lt;h1&gt;This is an about page&lt;/h1&gt;</pre><pre>   ...</pre><pre>&lt;/template&gt;</pre><pre>&lt;style lang=&quot;scss&quot;&gt;</pre><pre>   .about {</pre><pre>      @apply lg:min-h-screen lg:flex lg:items-center;<br></pre><pre>      h1 {</pre><pre>         @apply text-xl font-medium text-white;</pre><pre>      }</pre><pre>   }</pre><pre>&lt;/style&gt;</pre><p>🎉 Awesome! We have our core and we have our looks. What else could we add? 🤔</p><h3>Add SVG loader (optional)</h3><p><em>I like my SVG like I like my app — Component-Driven.🥁 It just so happens that our newly stacked boilerplate can easily import SVG imagery, but there is a catch — you’ll have to use it as component, meaning you’ll manually have to add SVG code within template tags and import it like that.</em></p><p>Luckily there is this <a href="https://www.npmjs.com/package/vite-svg-loader"><strong>vite-svg-loader</strong></a> package that basically allows you to simply import your .svg files within Vue template as components. Let’s proceed by adding it to our boilerplate:</p><pre>npm install vite-svg-loader --save-dev</pre><p>Now add this plugin in our vite.config.js configuration file:</p><pre>...</pre><pre>import svgLoader from &#39;vite-svg-loader&#39;</pre><pre>export default defineConfig({</pre><pre>   plugins: [vue(), svgLoader()],</pre><pre>   ...</pre><pre>})</pre><p>Lastly, to test it out I’m going to change that <strong>Vue.js</strong> logo code to <strong>Vite.js </strong>in /src/assets/logo.svg to <a href="https://worldvectorlogo.com/download/vitejs.svg">this one</a> and save it:</p><pre>&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; viewBox=&quot;0 0 2454.32 2457.41&quot;&gt;&lt;defs&gt;&lt;linearGradient id=&quot;a&quot; x1=&quot;285.11&quot; y1=&quot;1790.44&quot; x2=&quot;285.7&quot; y2=&quot;1789.74&quot; gradientTransform=&quot;matrix(2454.32, 0, 0, -2187.24, -699180.9, 3916163.49)&quot; gradientUnits=&quot;userSpaceOnUse&quot;&gt;&lt;stop offset=&quot;0&quot; stop-color=&quot;#41d1ff&quot;/&gt;&lt;stop offset=&quot;1&quot; stop-color=&quot;#bd34fe&quot;/&gt;&lt;/linearGradient&gt;&lt;linearGradient id=&quot;b&quot; x1=&quot;285.22&quot; y1=&quot;1790.33&quot; x2=&quot;285.29&quot; y2=&quot;1789.46&quot; gradientTransform=&quot;matrix(1125.42, 0, 0, -2051.66, -319596.68, 3673197.31)&quot; gradientUnits=&quot;userSpaceOnUse&quot;&gt;&lt;stop offset=&quot;0&quot; stop-color=&quot;#ffea83&quot;/&gt;&lt;stop offset=&quot;0.08&quot; stop-color=&quot;#ffdd35&quot;/&gt;&lt;stop offset=&quot;1&quot; stop-color=&quot;#ffa800&quot;/&gt;&lt;/linearGradient&gt;&lt;/defs&gt;&lt;path d=&quot;M2464.14,381.6,1311.22,2443.21c-23.8,42.57-85,42.82-109.12.46L26.33,381.79C0,335.63,39.47,279.72,91.78,289.08L1245.93,495.37a62.88,62.88,0,0,0,22.27,0l1130-206C2450.35,279.87,2490,335.35,2464.14,381.6Z&quot; transform=&quot;translate(-17.94 -17.87)&quot; style=&quot;fill:url(#a)&quot;/&gt;&lt;path d=&quot;M1795.71,18.48,942.53,185.66a31.33,31.33,0,0,0-25.25,28.9L864.8,1101a31.33,31.33,0,0,0,29.41,33.14,31.77,31.77,0,0,0,8.91-.75l237.54-54.82a31.32,31.32,0,0,1,37.73,36.79l-70.57,345.59a31.33,31.33,0,0,0,39.8,36.24l146.72-44.57a31.34,31.34,0,0,1,39.79,36.32L1222,2031.73c-7,33.95,38.14,52.47,57,23.36l12.59-19.44L1986.77,648.19c11.65-23.23-8.44-49.72-33.94-44.79l-244.52,47.18a31.33,31.33,0,0,1-36-39.44L1831.86,57.91a31.34,31.34,0,0,0-36.14-39.43Z&quot; transform=&quot;translate(-17.94 -17.87)&quot; style=&quot;fill:url(#b)&quot;/&gt;&lt;/svg&gt;</pre><p>Then in /src/App.vue file I’ll import it as SVG component and replace it with &lt;img class=”logo” /&gt;.</p><pre>&lt;script setup&gt;</pre><pre>   ...</pre><pre>   import LogoSVG from &#39;./assets/logo.svg?component&#39;</pre><pre>&lt;/script&gt;</pre><pre>&lt;template&gt;</pre><pre>   ...</pre><pre>   &lt;LogoSVG alt=&quot;Vite logo&quot; class=&quot;logo&quot; /&gt;</pre><pre>   ...</pre><pre>&lt;/template&gt;</pre><h3>🎉Congratulations<strong>!</strong></h3><p>You are a proud owner of your very own boilerplate. 👏</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FvvbGMpbhZMcHSsD50w%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FvvbGMpbhZMcHSsD50w%2Fgiphy.gif&amp;image=https%3A%2F%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExOHFsdXJqaXdmaDZ4eDV6dDlmOXo0c2I0dm1xYmw3MDczcWQ2a3dqYyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FvvbGMpbhZMcHSsD50w%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="326" frameborder="0" scrolling="no"><a href="https://medium.com/media/818bd659e03d0e44aeea9a852090169a/href">https://medium.com/media/818bd659e03d0e44aeea9a852090169a/href</a></iframe><h3>What’s next?</h3><p>You would now want to create an easily <em>pull-able</em> starter kit — for example, in <strong>Github</strong> — so your boilerplate is always one command away. I actually did just that — you can pull my version of boilerplate <a href="https://github.com/richardevcom/vue3-boilerplate">here</a> or simply pull it:</p><pre>npm i @richardev/vue3-boilerplate</pre><p>Finally, I want to share this list of some useful <strong>Vue.js</strong> related packages to add to your boilerplate:</p><ul><li><a href="https://nuxtjs.org/"><strong>NuxtJS</strong></a><strong> </strong>—<strong> Vue.js</strong> Framework</li><li><a href="https://vue-meta.nuxtjs.org/"><strong>Vue Meta</strong></a> — for Web App SEO</li><li><a href="https://vee-validate.logaretm.com/v4/"><strong>VeeValidate</strong></a> — form validation</li><li><a href="https://vue-toastification.maronato.dev/"><strong>Vue Toastification</strong></a> — alerts a.k. toasts</li></ul><p><em>If you have ideas on what to add or remove in this boilerplate, tips on configuring it or you simply have an issue setting it up — I’ll appreciate if you reach out in the comments below. 👋</em></p><blockquote>⚡<strong>Update</strong>: I’ve recreated <strong>TypeScript </strong>ready version of this boilerplate <a href="https://github.com/richardevcom/vue-ts-boilerplate"><strong>here</strong></a>.</blockquote><p><a href="https://github.com/sponsors/richardevcom?o=sd&amp;sc=t">Sponsor @richardevcom on GitHub Sponsors</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9635806acde3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/js-dojo/custom-vue3-boilerplate-9635806acde3">Custom Vue 3 boilerplate — Vite, Pinia, Vue Router &amp; Tailwind CSS</a> was originally published in <a href="https://medium.com/js-dojo">Vue.js Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>