<?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: Ishan Jarwal</title>
    <description>The latest articles on DEV Community by Ishan Jarwal (@ishanjarwal).</description>
    <link>https://dev.to/ishanjarwal</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%2F3954467%2F2478626d-f163-4c23-8349-c86e92fdc102.jpg</url>
      <title>DEV Community: Ishan Jarwal</title>
      <link>https://dev.to/ishanjarwal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ishanjarwal"/>
    <language>en</language>
    <item>
      <title>Asynchronous Jobs made easy with Inngest | Learn how to setup Inngest in a Typescript Project</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Tue, 22 Sep 2026 18:01:50 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/asynchronous-jobs-made-easy-with-inngest-learn-how-to-setup-inngest-in-a-typescript-project-50h0</link>
      <guid>https://dev.to/ishanjarwal/asynchronous-jobs-made-easy-with-inngest-learn-how-to-setup-inngest-in-a-typescript-project-50h0</guid>
      <description>&lt;h2&gt;
  
  
  Inngest with Next.js and TypeScript: A Practical Guide to Background Jobs, Workflows, Retries, and Debugging
&lt;/h2&gt;

&lt;p&gt;If you've ever written a Next.js API route that starts doing too much, you've probably run into this problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The user doesn't actually need to wait for this."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe you're sending an email after signup.&lt;/p&gt;

&lt;p&gt;Maybe you're generating a PDF.&lt;/p&gt;

&lt;p&gt;Maybe you're processing uploaded images.&lt;/p&gt;

&lt;p&gt;Maybe you're syncing data with Stripe, Shopify, a CRM, or some third-party API.&lt;/p&gt;

&lt;p&gt;Or maybe you've got a workflow that looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User signs up
   ↓
Create account
   ↓
Send welcome email
   ↓
Create CRM contact
   ↓
Wait 2 days
   ↓
Send follow-up email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; put all of this inside a regular Next.js request.&lt;/p&gt;

&lt;p&gt;But eventually you'll start worrying about timeouts, retries, failed API calls, duplicated work, cron jobs, queues, workers, and what happens if your server dies halfway through the process.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;Inngest&lt;/strong&gt; becomes useful.&lt;/p&gt;

&lt;p&gt;Inngest gives you an event-driven way of running background jobs and durable workflows without having to build and maintain your own queue infrastructure. It provides things like retries, steps, delayed execution, concurrency controls, observability, and execution history.&lt;/p&gt;

&lt;p&gt;This guide walks through setting it up from scratch in a TypeScript Next.js application and, more importantly, explains &lt;strong&gt;what each file is doing and why it exists&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we're going to build
&lt;/h2&gt;

&lt;p&gt;By the end, we'll have a Next.js application that looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js application
       │
       │ sends event
       ▼
   Inngest
       │
       │ triggers
       ▼
 Inngest Function
       │
       ├── Step 1
       ├── Step 2
       ├── Step 3
       └── Step 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll build a small example around a user signup.&lt;/p&gt;

&lt;p&gt;When a user signs up, we'll send an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/user.created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inngest will receive that event and trigger a function.&lt;/p&gt;

&lt;p&gt;That function will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch the user.&lt;/li&gt;
&lt;li&gt;Send a welcome email.&lt;/li&gt;
&lt;li&gt;Update some external system.&lt;/li&gt;
&lt;li&gt;Wait for a short period.&lt;/li&gt;
&lt;li&gt;Send a follow-up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The individual pieces will be represented as Inngest &lt;strong&gt;steps&lt;/strong&gt;, which gives us retryability and durable execution.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. First, what problem is Inngest actually solving?
&lt;/h1&gt;

&lt;p&gt;Before installing anything, it's worth understanding the mental model.&lt;/p&gt;

&lt;p&gt;Suppose you have this API route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUser&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;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncWithCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;But your HTTP request now depends on three things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createUser
    ↓
sendWelcomeEmail
    ↓
syncWithCRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;syncWithCRM()&lt;/code&gt; takes 10 seconds, the request takes 10 seconds.&lt;/p&gt;

&lt;p&gt;If the CRM is temporarily unavailable, the whole request can fail.&lt;/p&gt;

&lt;p&gt;If the email succeeds but the CRM fails, you have to decide what happens when the client retries the original request.&lt;/p&gt;

&lt;p&gt;That's where background execution starts becoming valuable.&lt;/p&gt;

&lt;p&gt;With Inngest, your request can instead do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP request can finish.&lt;/p&gt;

&lt;p&gt;Inngest then takes over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP request
    │
    └── publish event
             │
             ▼
        Inngest
             │
             ▼
      user-created function
             │
             ├── fetch user
             ├── send email
             ├── sync CRM
             └── follow-up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the basic idea.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. When should you use Inngest?
&lt;/h1&gt;

&lt;p&gt;A useful rule of thumb is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the user doesn't need to wait for the operation, it's a good candidate for Inngest.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending emails&lt;/li&gt;
&lt;li&gt;Processing uploaded files&lt;/li&gt;
&lt;li&gt;Generating reports&lt;/li&gt;
&lt;li&gt;Image/video processing&lt;/li&gt;
&lt;li&gt;Webhook processing&lt;/li&gt;
&lt;li&gt;Stripe-related background work&lt;/li&gt;
&lt;li&gt;CRM synchronization&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Scheduled jobs&lt;/li&gt;
&lt;li&gt;Data synchronization&lt;/li&gt;
&lt;li&gt;AI workflows&lt;/li&gt;
&lt;li&gt;Long-running processes&lt;/li&gt;
&lt;li&gt;Multi-step workflows&lt;/li&gt;
&lt;li&gt;Retryable API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inngest functions can be triggered by events, schedules, and other workflow mechanisms. Steps can also pause, wait for events, invoke other functions, and retry independently.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Prerequisites
&lt;/h1&gt;

&lt;p&gt;For this guide, I'm assuming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js is installed&lt;/li&gt;
&lt;li&gt;You have a TypeScript Next.js project&lt;/li&gt;
&lt;li&gt;You're using the Next.js App Router&lt;/li&gt;
&lt;li&gt;Your project uses npm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js
TypeScript
App Router
src/ directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're starting from scratch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest inngest-nextjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;inngest-nextjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And start your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  4. Install Inngest
&lt;/h1&gt;

&lt;p&gt;Inside your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;inngest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need for the SDK.&lt;/p&gt;

&lt;p&gt;The current Inngest Next.js quick start also uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;inngest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for the TypeScript SDK.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. The folder structure
&lt;/h1&gt;

&lt;p&gt;Now let's organize the project.&lt;/p&gt;

&lt;p&gt;There are many ways to structure Inngest code. You don't have to follow this exact structure, but this is a clean starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app/
│   ├── api/
│   │   └── inngest/
│   │       └── route.ts
│   │
│   └── page.tsx
│
├── inngest/
│   ├── client.ts
│   ├── functions/
│   │   ├── user-created.ts
│   │   └── index.ts
│   └── index.ts
│
└── lib/
    ├── email.ts
    ├── db.ts
    └── crm.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't necessarily need all these files on day one.&lt;/p&gt;

&lt;p&gt;The important pieces are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inngest/client.ts
inngest/functions/*
app/api/inngest/route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at what each one does.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. &lt;code&gt;src/inngest/client.ts&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;This file creates your Inngest client.&lt;/p&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/inngest/client.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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;Inngest&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="s2"&gt;inngest&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;const&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Inngest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-nextjs-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;This client becomes the central object used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create Inngest functions&lt;/li&gt;
&lt;li&gt;send events&lt;/li&gt;
&lt;li&gt;configure your application identity&lt;/li&gt;
&lt;li&gt;communicate with Inngest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of this file as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This is my application talking to Inngest."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; should represent your application.&lt;/p&gt;

&lt;p&gt;For 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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Inngest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acme-web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the ID stable. Function IDs should also remain stable between deployments because Inngest uses them to identify functions.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Create your first Inngest function
&lt;/h1&gt;

&lt;p&gt;Now let's make the interesting part.&lt;/p&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/inngest/functions/user-created.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll start with something simple.&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;inngest&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="s2"&gt;../client&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;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&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="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="s2"&gt;New user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important pieces are:&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="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the function.&lt;/p&gt;

&lt;p&gt;Then:&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives the function a stable identifier.&lt;/p&gt;

&lt;p&gt;And:&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Run this function whenever an &lt;code&gt;app/user.created&lt;/code&gt; event happens."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Inngest's current SDK documentation uses the v4 function configuration style with &lt;code&gt;triggers&lt;/code&gt;, so the equivalent current form is:&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;inngest&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="s2"&gt;../client&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;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&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;Use the &lt;strong&gt;v4 form&lt;/strong&gt; in new projects.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Export your functions
&lt;/h1&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/inngest/functions/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userCreated&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="s2"&gt;./user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may seem unnecessary when you only have one function.&lt;/p&gt;

&lt;p&gt;But imagine your application eventually has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;functions/
├── user-created.ts
├── send-welcome-email.ts
├── process-payment.ts
├── generate-report.ts
├── sync-crm.ts
└── cleanup-expired-data.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having one central export makes the route much cleaner.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Create the Inngest API route
&lt;/h1&gt;

&lt;p&gt;This is the part that often confuses people.&lt;/p&gt;

&lt;p&gt;Your Next.js application needs an HTTP endpoint that Inngest can communicate with.&lt;/p&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/app/api/inngest/route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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;serve&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="s2"&gt;inngest/next&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;inngest&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="s2"&gt;@/inngest/client&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;userCreated&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="s2"&gt;@/inngest/functions&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;const&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;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userCreated&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;If you have multiple functions:&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;serve&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="s2"&gt;inngest/next&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;inngest&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="s2"&gt;@/inngest/client&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;userCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&gt;@/inngest/functions&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;const&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;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;userCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;processPayment&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;This route is normally available at:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;serve()&lt;/code&gt; handler allows Inngest to discover your functions and invoke them through your application.&lt;/p&gt;

&lt;p&gt;So conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inngest
   │
   │ HTTP
   ▼
/api/inngest
   │
   ▼
Next.js
   │
   ├── userCreated
   ├── sendWelcomeEmail
   └── processPayment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint is one of the most important pieces of the setup.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Your project should now look something like this
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app/
│   └── api/
│       └── inngest/
│           └── route.ts
│
└── inngest/
    ├── client.ts
    │
    └── functions/
        ├── index.ts
        └── user-created.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And each file has a very specific responsibility:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inngest/client.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates the Inngest client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inngest/functions/*.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains your actual background functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inngest/functions/index.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exports all functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app/api/inngest/route.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exposes the functions to Inngest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's really the core architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Run the Inngest Dev Server
&lt;/h1&gt;

&lt;p&gt;This is where Inngest gets really nice during development.&lt;/p&gt;

&lt;p&gt;You don't need to immediately create a cloud project just to test your function.&lt;/p&gt;

&lt;p&gt;Inngest provides a local Dev Server that lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;discover your functions&lt;/li&gt;
&lt;li&gt;send events&lt;/li&gt;
&lt;li&gt;inspect runs&lt;/li&gt;
&lt;li&gt;inspect errors&lt;/li&gt;
&lt;li&gt;view step execution&lt;/li&gt;
&lt;li&gt;rerun functions&lt;/li&gt;
&lt;li&gt;cancel runs&lt;/li&gt;
&lt;li&gt;debug workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current Dev Server runs on port &lt;code&gt;8288&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;You can start it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx inngest-cli@latest dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install the CLI and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;inngest dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8288
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the Inngest development UI.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Run Next.js in development mode
&lt;/h1&gt;

&lt;p&gt;In another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the current SDK, you can explicitly tell Inngest that you're working locally:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Put that in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So your &lt;code&gt;.env.local&lt;/code&gt; might contain:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;In Dev Mode, the SDK communicates with your local Inngest Dev Server rather than Inngest Cloud. Signing keys aren't required for the local Dev Server.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Trigger your first event
&lt;/h1&gt;

&lt;p&gt;Now we need to actually send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/user.created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several ways to do this.&lt;/p&gt;

&lt;p&gt;The simplest is from your server-side application code.&lt;/p&gt;

&lt;p&gt;For 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;inngest&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="s2"&gt;@/inngest/client&lt;/span&gt;&lt;span class="dl"&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;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_123&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the event.&lt;/p&gt;

&lt;p&gt;Notice the event structure:&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="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; is what determines which functions are triggered.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; is the payload your function receives.&lt;/p&gt;

&lt;p&gt;Inside your function:&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&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;you'll get:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Event keys are required for sending events to Inngest Cloud in production, but they're not required when using the local Dev Server.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. A more realistic example
&lt;/h1&gt;

&lt;p&gt;Let's make this useful.&lt;/p&gt;

&lt;p&gt;Suppose a user registers.&lt;/p&gt;

&lt;p&gt;Our application creates the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&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="nx"&gt;user&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;name&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;Then we don't want to make the HTTP request wait for everything else.&lt;/p&gt;

&lt;p&gt;So:&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;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our Inngest function can handle the rest.&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;inngest&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="s2"&gt;../client&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;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&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="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;user&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-user&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="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;// Fetch the user from your database&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-welcome-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-crm&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncUserWithCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current v4 syntax is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;// ...&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;Again, use this v4 form for a new project.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Why &lt;code&gt;step.run()&lt;/code&gt; matters
&lt;/h1&gt;

&lt;p&gt;This is probably the most important Inngest concept to understand.&lt;/p&gt;

&lt;p&gt;You might initially write:&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;await&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But with Inngest, you'd normally structure meaningful side effects as steps:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-welcome-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-crm&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because steps are independently tracked and retried.&lt;/p&gt;

&lt;p&gt;If this happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get-user        ✓
send-email      ✓
sync-crm        ✗
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inngest doesn't need to blindly start the whole function from scratch.&lt;/p&gt;

&lt;p&gt;The completed work is checkpointed, and the failed step can be retried.&lt;/p&gt;

&lt;p&gt;This is one of the core ideas behind Inngest's durable execution model.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Don't put important side effects outside steps
&lt;/h1&gt;

&lt;p&gt;This is a very important practical rule.&lt;/p&gt;

&lt;p&gt;Avoid:&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;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save-user&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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;If your function is replayed, the email operation isn't represented as an Inngest step.&lt;/p&gt;

&lt;p&gt;Prefer:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save-user&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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;Database writes, API calls, emails, payment operations, and similar side effects are usually good candidates for &lt;code&gt;step.run()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inngest specifically recommends putting nondeterministic side effects such as database writes and API calls inside steps so completed work can be checkpointed.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. Retries
&lt;/h1&gt;

&lt;p&gt;One of the nicest things about steps is retry behavior.&lt;/p&gt;

&lt;p&gt;Imagine:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;emailProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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;If your email provider temporarily returns an error, the step can be retried according to your function's retry configuration.&lt;/p&gt;

&lt;p&gt;Each step has its own retry counter.&lt;/p&gt;

&lt;p&gt;That's much more useful than writing this everywhere:&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;try&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;something&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;something&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;You don't want retry logic scattered throughout your application.&lt;/p&gt;

&lt;p&gt;Let your workflow layer handle it.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Sleeping without keeping a server request open
&lt;/h1&gt;

&lt;p&gt;This is another area where Inngest becomes particularly useful.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User signs up
      ↓
Send welcome email
      ↓
Wait 3 days
      ↓
Send onboarding email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't want to keep a Node.js process sitting around for three days.&lt;/p&gt;

&lt;p&gt;With Inngest:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wait-before-follow-up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3 days&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-follow-up&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendFollowUpEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function can pause and resume later rather than keeping a normal HTTP request alive.&lt;/p&gt;

&lt;p&gt;The SDK provides step APIs including &lt;code&gt;sleep&lt;/code&gt;, &lt;code&gt;sleepUntil&lt;/code&gt;, &lt;code&gt;waitForEvent&lt;/code&gt;, &lt;code&gt;invoke&lt;/code&gt;, and &lt;code&gt;sendEvent&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. A complete workflow example
&lt;/h1&gt;

&lt;p&gt;Here's what a slightly more realistic onboarding workflow could 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;inngest&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="s2"&gt;../client&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;const&lt;/span&gt; &lt;span class="nx"&gt;onboardingWorkflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-onboarding&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="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;user&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-user&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-welcome-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create-crm-contact&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createCRMContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wait-before-onboarding-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3 days&lt;/span&gt;&lt;span class="dl"&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-onboarding-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendOnboardingEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your workflow is readable almost like a checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get user
   ↓
Send welcome email
   ↓
Create CRM contact
   ↓
Wait 3 days
   ↓
Send onboarding email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's really one of the strengths of Inngest.&lt;/p&gt;

&lt;p&gt;The code describes the workflow instead of forcing you to build your own queue/state machine infrastructure.&lt;/p&gt;




&lt;h1&gt;
  
  
  20. Environment variables
&lt;/h1&gt;

&lt;p&gt;There are a few Inngest environment variables worth knowing.&lt;/p&gt;

&lt;p&gt;For local development:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;For production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INNGEST_EVENT_KEY=...
INNGEST_SIGNING_KEY=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Potentially:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;during signing-key rotation.&lt;/p&gt;

&lt;p&gt;There are also configuration variables such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INNGEST_BASE_URL=...
INNGEST_ENV=...
INNGEST_LOG_LEVEL=...
INNGEST_SERVE_ORIGIN=...
INNGEST_SERVE_PATH=...
INNGEST_STREAMING=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not every application needs all of these.&lt;/p&gt;

&lt;p&gt;A typical production setup might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INNGEST_EVENT_KEY=your-event-key
INNGEST_SIGNING_KEY=your-signing-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep these secrets out of Git.&lt;/p&gt;

&lt;p&gt;Don't do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Inngest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signingKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;super-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer environment variables.&lt;/p&gt;

&lt;p&gt;Inngest explicitly recommends using &lt;code&gt;INNGEST_SIGNING_KEY&lt;/code&gt; instead of hard-coding the signing key.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. Event Key vs Signing Key
&lt;/h1&gt;

&lt;p&gt;These two are easy to mix up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Key
&lt;/h3&gt;

&lt;p&gt;An Event Key is used by your application to &lt;strong&gt;send events to Inngest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For 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;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, the SDK can use:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Event keys are environment-specific.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signing Key
&lt;/h3&gt;

&lt;p&gt;The Signing Key is about securely communicating between your deployed application and Inngest.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVENT KEY
   ↓
"My application can send events"

SIGNING KEY
   ↓
"Secure communication between Inngest and my app"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't confuse the two.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. What happens in production?
&lt;/h1&gt;

&lt;p&gt;Locally you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js
localhost:3000

       ↕

Inngest Dev Server
localhost:8288
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, the architecture is closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Next.js deployment
        │
        │ /api/inngest
        │
        ▼
   Inngest Cloud
        │
        │
        ▼
  Function execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For serverless platforms such as Vercel, the normal approach is to expose the &lt;code&gt;serve()&lt;/code&gt; handler through your application at &lt;code&gt;/api/inngest&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. Deploying with Vercel
&lt;/h1&gt;

&lt;p&gt;If you're using Next.js, Vercel is probably the deployment platform you'll encounter most often.&lt;/p&gt;

&lt;p&gt;Your application already has:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;So after deploying the application, Inngest needs to be able to reach that endpoint.&lt;/p&gt;

&lt;p&gt;For production, configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INNGEST_EVENT_KEY=...
INNGEST_SIGNING_KEY=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in your production environment.&lt;/p&gt;

&lt;p&gt;Inngest also provides a Vercel integration that can help configure the necessary integration.&lt;/p&gt;

&lt;p&gt;The exact deployment workflow can change depending on your Vercel/Inngest setup, so when deploying a real application, it's worth checking the current Inngest deployment documentation rather than blindly copying an old blog post.&lt;/p&gt;




&lt;h1&gt;
  
  
  24. Inngest environments
&lt;/h1&gt;

&lt;p&gt;This is something you should understand before your project becomes a team project.&lt;/p&gt;

&lt;p&gt;Inngest supports separate environments, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production&lt;/li&gt;
&lt;li&gt;Branch environments&lt;/li&gt;
&lt;li&gt;Custom environments&lt;/li&gt;
&lt;li&gt;Local development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is isolation.&lt;/p&gt;

&lt;p&gt;You don't want your local testing to accidentally trigger production workflows.&lt;/p&gt;

&lt;p&gt;Each environment has isolated event/function data and its own keys.&lt;/p&gt;

&lt;p&gt;A typical team setup might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local
  ↓
Dev Server

Feature branch
  ↓
Branch environment

Staging
  ↓
Custom environment

Production
  ↓
Production environment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're working alone, you can start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local → Production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and introduce additional environments when you actually need them.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. The Inngest Portal / Dashboard
&lt;/h1&gt;

&lt;p&gt;Once you're working with Inngest Cloud, the dashboard becomes your control room.&lt;/p&gt;

&lt;p&gt;You'll typically use it to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applications&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;li&gt;Events&lt;/li&gt;
&lt;li&gt;Function runs&lt;/li&gt;
&lt;li&gt;Errors&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Execution traces&lt;/li&gt;
&lt;li&gt;Environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important thing to remember is that the dashboard isn't just a place to see whether something succeeded.&lt;/p&gt;

&lt;p&gt;It's where you can investigate &lt;strong&gt;why&lt;/strong&gt; it succeeded or failed.&lt;/p&gt;

&lt;p&gt;Inngest's run view provides information about the event payload, execution timeline, steps, timings, errors, and retries.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Debugging locally
&lt;/h1&gt;

&lt;p&gt;When something doesn't work, don't immediately start adding random &lt;code&gt;console.log()&lt;/code&gt; calls everywhere.&lt;/p&gt;

&lt;p&gt;First open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8288
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and look at the run.&lt;/p&gt;

&lt;p&gt;You should be able to see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function
   ↓
Run
   ↓
Step 1
   ↓
Step 2
   ↓
Step 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a step fails, inspect that step.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

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

✓ get-user
✓ send-welcome-email
✗ sync-crm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you know exactly where the problem is.&lt;/p&gt;

&lt;p&gt;That's much easier than looking at a giant application log.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. Debugging production failures
&lt;/h1&gt;

&lt;p&gt;Suppose you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync-crm
FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go into the Inngest dashboard and open the run.&lt;/p&gt;

&lt;p&gt;You can inspect the execution timeline and the failed step.&lt;/p&gt;

&lt;p&gt;The dashboard can show individual attempts and the errors associated with them. It also provides options for rerunning runs and, in some cases, replaying from a step.&lt;/p&gt;

&lt;p&gt;That gives you a workflow like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production failure
      ↓
Open Inngest
      ↓
Find failed run
      ↓
Open trace
      ↓
Find failed step
      ↓
Read error
      ↓
Fix code
      ↓
Deploy
      ↓
Rerun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a much nicer debugging experience than manually reconstructing what happened from logs.&lt;/p&gt;




&lt;h1&gt;
  
  
  28. Use meaningful step IDs
&lt;/h1&gt;

&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step-1&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="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;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works.&lt;/p&gt;

&lt;p&gt;But this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create-crm-contact&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="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;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is much better.&lt;/p&gt;

&lt;p&gt;When you open the dashboard later, you immediately understand what's happening.&lt;/p&gt;

&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch-user
send-welcome-email
create-crm-contact
update-subscription
generate-invoice
send-invoice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;step-1
step-2
step-3
step-4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The step ID is also part of how Inngest identifies and memoizes the step, so don't casually change IDs between versions.&lt;/p&gt;




&lt;h1&gt;
  
  
  29. Logging
&lt;/h1&gt;

&lt;p&gt;You can still use normal logging:&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="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="s2"&gt;Processing user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But inside an Inngest function you can also use the provided logger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&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="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Processing user&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="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK exposes logging interfaces such as &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;warn&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, and &lt;code&gt;debug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Keep logs useful.&lt;/p&gt;

&lt;p&gt;Instead of:&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="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="s2"&gt;here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prefer:&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Starting CRM synchronization&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;userId&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;Six months later, you'll thank yourself.&lt;/p&gt;




&lt;h1&gt;
  
  
  30. A note about retries and duplicate side effects
&lt;/h1&gt;

&lt;p&gt;Retries are great.&lt;/p&gt;

&lt;p&gt;But retries also mean you need to think about &lt;strong&gt;idempotency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For 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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;charge-customer&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charges&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't want a retry to accidentally charge somebody twice.&lt;/p&gt;

&lt;p&gt;The same idea applies to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payments&lt;/li&gt;
&lt;li&gt;sending emails&lt;/li&gt;
&lt;li&gt;creating external records&lt;/li&gt;
&lt;li&gt;sending notifications&lt;/li&gt;
&lt;li&gt;database mutations&lt;/li&gt;
&lt;li&gt;webhook handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever an operation isn't naturally idempotent, use an idempotency key or design the operation so repeating it is safe.&lt;/p&gt;

&lt;p&gt;For 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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create-crm-contact&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contacts&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="na"&gt;externalId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;Your CRM integration might then use &lt;code&gt;externalId&lt;/code&gt; to avoid creating duplicate contacts.&lt;/p&gt;

&lt;p&gt;This isn't an Inngest-specific problem.&lt;/p&gt;

&lt;p&gt;It's simply something you need to think about whenever work can be retried.&lt;/p&gt;




&lt;h1&gt;
  
  
  31. Don't make one giant step
&lt;/h1&gt;

&lt;p&gt;You could write:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;everything&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&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;syncCRM&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;generateReport&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;notifySlack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now Inngest sees this as one unit of work.&lt;/p&gt;

&lt;p&gt;If the Slack notification fails, the whole block may need to run again.&lt;/p&gt;

&lt;p&gt;Instead:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-crm&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncCRM&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generate-report&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateReport&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notify-slack&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySlack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you get a much clearer workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;send-email        ✓
sync-crm          ✓
generate-report   ✓
notify-slack      ✗
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's exactly the kind of execution history you want when debugging.&lt;/p&gt;




&lt;h1&gt;
  
  
  32. But don't create a step for every line either
&lt;/h1&gt;

&lt;p&gt;The opposite extreme is also unnecessary.&lt;/p&gt;

&lt;p&gt;You don't need:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create-variable&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uppercase-variable&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;log-variable&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="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="nx"&gt;value&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;Steps are most useful around meaningful units of work.&lt;/p&gt;

&lt;p&gt;A good step is usually something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch data
write database
call external API
send email
generate document
charge payment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think in terms of &lt;strong&gt;units that you would want to retry or inspect independently&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  33. Sending multiple events
&lt;/h1&gt;

&lt;p&gt;Your application can publish events whenever something interesting happens.&lt;/p&gt;

&lt;p&gt;For 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;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/order.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&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;Then a function can listen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;process-order&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="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/order.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;// process order&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a useful architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Business event
      │
      ▼
Inngest
      │
      ├── send confirmation email
      │
      ├── update analytics
      │
      ├── sync inventory
      │
      └── notify fulfillment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different functions can react to the same event.&lt;/p&gt;

&lt;p&gt;That helps keep your application loosely coupled.&lt;/p&gt;




&lt;h1&gt;
  
  
  34. Event naming conventions
&lt;/h1&gt;

&lt;p&gt;Don't randomly name events.&lt;/p&gt;

&lt;p&gt;Pick a convention and stick to it.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/user.created
app/user.deleted
app/order.created
app/order.paid
app/order.cancelled
app/subscription.created
app/subscription.cancelled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A nice pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain/entity.action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/user.created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;It becomes much easier to understand as the number of events grows.&lt;/p&gt;




&lt;h1&gt;
  
  
  35. Cron jobs
&lt;/h1&gt;

&lt;p&gt;Inngest isn't only for events.&lt;/p&gt;

&lt;p&gt;Scheduled functions are another common use case.&lt;/p&gt;

&lt;p&gt;For example, you might want to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every day at 2 AM
      ↓
Find expired subscriptions
      ↓
Update database
      ↓
Send notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function can be configured with a cron trigger.&lt;/p&gt;

&lt;p&gt;This is particularly useful when you have scheduled tasks that would otherwise require separate cron infrastructure.&lt;/p&gt;

&lt;p&gt;Inngest's function model supports cron/background jobs as well as event-driven workflows.&lt;/p&gt;




&lt;h1&gt;
  
  
  36. Concurrency and throttling
&lt;/h1&gt;

&lt;p&gt;As your application grows, you'll eventually run into another problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What happens if 10,000 events arrive at once?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don't necessarily want 10,000 copies of the same function hammering your API or database.&lt;/p&gt;

&lt;p&gt;Inngest supports flow-control features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concurrency&lt;/li&gt;
&lt;li&gt;throttling&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;debouncing&lt;/li&gt;
&lt;li&gt;prioritization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a function can be configured with a throttle:&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="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-systems&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/sync.requested&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;throttle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1m&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact configuration should be chosen based on the limits of the systems you're calling.&lt;/p&gt;

&lt;p&gt;Inngest documents these flow-control features as part of its function model.&lt;/p&gt;




&lt;h1&gt;
  
  
  37. Testing Inngest functions
&lt;/h1&gt;

&lt;p&gt;Don't rely exclusively on the Dev Server.&lt;/p&gt;

&lt;p&gt;You should also test your functions in your automated test suite.&lt;/p&gt;

&lt;p&gt;Inngest provides testing utilities for executing functions and inspecting step state.&lt;/p&gt;

&lt;p&gt;For example, its testing API can expose the state/results of individual steps, which makes it possible to test both successful and failing workflows.&lt;/p&gt;

&lt;p&gt;This becomes particularly valuable for workflows containing business-critical logic.&lt;/p&gt;

&lt;p&gt;A useful testing strategy is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unit tests
   ↓
Individual business logic

Inngest tests
   ↓
Workflow behavior

Dev Server
   ↓
Manual integration testing

Production dashboard
   ↓
Real execution monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  38. Keep business logic separate from Inngest
&lt;/h1&gt;

&lt;p&gt;This is another architectural choice that pays off later.&lt;/p&gt;

&lt;p&gt;Instead of putting everything directly inside:&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="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can keep business logic elsewhere.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── inngest/
│   └── functions/
│       └── user-created.ts
│
└── services/
    ├── users.ts
    ├── email.ts
    └── crm.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then your function becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;user&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-user&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="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-email&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="nx"&gt;emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendWelcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-crm&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="nx"&gt;crmService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a much healthier separation.&lt;/p&gt;

&lt;p&gt;Inngest describes &lt;strong&gt;when and how the work runs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your services describe &lt;strong&gt;what the work actually does&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  39. A clean final folder structure
&lt;/h1&gt;

&lt;p&gt;For a medium-sized project, I would aim for something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
│
├── app/
│   ├── api/
│   │   └── inngest/
│   │       └── route.ts
│   │
│   ├── dashboard/
│   └── ...
│
├── inngest/
│   ├── client.ts
│   │
│   └── functions/
│       ├── index.ts
│       ├── user-created.ts
│       ├── order-created.ts
│       ├── process-payment.ts
│       └── daily-cleanup.ts
│
├── services/
│   ├── email.ts
│   ├── crm.ts
│   ├── payments.ts
│   └── users.ts
│
├── db/
│   └── ...
│
└── lib/
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API / Server Action
        │
        │ inngest.send()
        ▼
     Event
        │
        ▼
    Function
        │
        ├── Step
        ├── Step
        ├── Step
        └── Step
             │
             ▼
          Services
             │
             ├── Database
             ├── Email
             ├── CRM
             └── APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  40. A complete example
&lt;/h1&gt;

&lt;p&gt;Let's put the pieces together.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/inngest/client.ts&lt;/code&gt;
&lt;/h3&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;Inngest&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="s2"&gt;inngest&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;const&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Inngest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-nextjs-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;src/inngest/functions/user-created.ts&lt;/code&gt;
&lt;/h3&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;inngest&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="s2"&gt;../client&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;const&lt;/span&gt; &lt;span class="nx"&gt;userCreated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&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="s2"&gt;User Created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;user&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-user&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="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;// Replace with your real database call.&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&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="s2"&gt;user@example.com&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-welcome-email&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Sending welcome email to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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="c1"&gt;// await emailService.sendWelcomeEmail(user);&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-crm&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Syncing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with CRM`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// await crmService.createOrUpdateUser(user);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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;
  
  
  &lt;code&gt;src/inngest/functions/index.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userCreated&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="s2"&gt;./user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;src/app/api/inngest/route.ts&lt;/code&gt;
&lt;/h3&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;serve&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="s2"&gt;inngest/next&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;inngest&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="s2"&gt;@/inngest/client&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;userCreated&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="s2"&gt;@/inngest/functions&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;const&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;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userCreated&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;
  
  
  &lt;code&gt;.env.local&lt;/code&gt;
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Then start two terminals.&lt;/p&gt;

&lt;p&gt;Terminal 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terminal 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx inngest-cli@latest dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8288
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trigger:&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;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_123&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you should see the run appear in the Dev Server.&lt;/p&gt;




&lt;h1&gt;
  
  
  41. The mental model I recommend
&lt;/h1&gt;

&lt;p&gt;If you're new to Inngest, don't try to memorize every API.&lt;/p&gt;

&lt;p&gt;Just remember these five things:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Event
&lt;/h3&gt;

&lt;p&gt;Something happened.&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;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/order.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;orderId&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;
  
  
  2. Function
&lt;/h3&gt;

&lt;p&gt;Something should react to that event.&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="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Step
&lt;/h3&gt;

&lt;p&gt;This is a meaningful unit of work.&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-email&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="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;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Serve
&lt;/h3&gt;

&lt;p&gt;This exposes your functions to Inngest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functions&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;
  
  
  5. Dev Server / Dashboard
&lt;/h3&gt;

&lt;p&gt;This is where you inspect what actually happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost:8288
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's most of the foundation.&lt;/p&gt;




&lt;h1&gt;
  
  
  42. Common mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Mistake #1: Forgetting the &lt;code&gt;/api/inngest&lt;/code&gt; route
&lt;/h2&gt;

&lt;p&gt;You created your function:&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="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but forgot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/api/inngest/route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inngest needs a way to discover and invoke your functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #2: Running the wrong environment
&lt;/h2&gt;

&lt;p&gt;If you're developing locally, make sure your SDK is actually in Dev Mode:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Otherwise the SDK defaults to cloud mode unless configured otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #3: Hard-coding secrets
&lt;/h2&gt;

&lt;p&gt;Don't commit:&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="nx"&gt;signingKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #4: Putting side effects outside steps
&lt;/h2&gt;

&lt;p&gt;Instead of:&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;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCustomer&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save-order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prefer:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;charge-customer&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCustomer&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save-order&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mistake #5: Making one enormous step
&lt;/h2&gt;

&lt;p&gt;Avoid:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;do-everything&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="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;// 500 lines&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Break meaningful operations into separate steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #6: Ignoring idempotency
&lt;/h2&gt;

&lt;p&gt;If something can be retried, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What happens if this runs twice?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Especially for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payments&lt;/li&gt;
&lt;li&gt;emails&lt;/li&gt;
&lt;li&gt;database mutations&lt;/li&gt;
&lt;li&gt;external APIs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Mistake #7: Using meaningless function IDs
&lt;/h2&gt;

&lt;p&gt;Don't constantly change:&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-created-v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;unless you actually intend to create a different Inngest function.&lt;/p&gt;

&lt;p&gt;Stable IDs matter to Inngest's function identity and execution model.&lt;/p&gt;




&lt;h1&gt;
  
  
  43. What I'd do next in a real project
&lt;/h1&gt;

&lt;p&gt;Once the basic setup works, I'd add things in roughly this order:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Create a proper event naming convention
&lt;/h3&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/user.created
app/user.deleted
app/order.created
app/order.paid
app/order.refunded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 — Move business logic into services
&lt;/h3&gt;

&lt;p&gt;Keep Inngest functions relatively small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Add retries
&lt;/h3&gt;

&lt;p&gt;Use retries for operations that can temporarily fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Add idempotency
&lt;/h3&gt;

&lt;p&gt;Especially around payments and external APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 — Add structured logging
&lt;/h3&gt;

&lt;p&gt;Make production debugging easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 — Add concurrency controls
&lt;/h3&gt;

&lt;p&gt;Particularly for expensive workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 — Add automated tests
&lt;/h3&gt;

&lt;p&gt;Test important workflows before they become difficult to reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8 — Add staging/custom environments
&lt;/h3&gt;

&lt;p&gt;Once multiple people or deployments are involved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9 — Set up production monitoring
&lt;/h3&gt;

&lt;p&gt;Learn to use the Inngest dashboard rather than relying only on application logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10 — Consider more advanced workflow features
&lt;/h3&gt;

&lt;p&gt;Once the basics feel comfortable, look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cron jobs&lt;/li&gt;
&lt;li&gt;delayed execution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step.sleep()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step.waitForEvent()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step.invoke()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;batching&lt;/li&gt;
&lt;li&gt;concurrency&lt;/li&gt;
&lt;li&gt;throttling&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;cancellation&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;durable endpoints&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  44. A note about the Inngest portal
&lt;/h1&gt;

&lt;p&gt;When you move beyond local development, don't think of the Inngest dashboard as just another admin page.&lt;/p&gt;

&lt;p&gt;It becomes part of your operational workflow.&lt;/p&gt;

&lt;p&gt;A typical debugging session might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer reports something went wrong
              ↓
Find the relevant function
              ↓
Find the run
              ↓
Inspect event payload
              ↓
Open execution trace
              ↓
Find failed step
              ↓
Inspect retry attempts
              ↓
Fix application
              ↓
Deploy
              ↓
Rerun the function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard's run details are designed specifically for this kind of investigation.&lt;/p&gt;

&lt;p&gt;And because Inngest automatically traces function runs, you don't need to build your own complete workflow tracing system just to understand what happened.&lt;/p&gt;




&lt;h1&gt;
  
  
  45. One last architectural picture
&lt;/h1&gt;

&lt;p&gt;If you remember nothing else from this guide, remember this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    NEXT.JS
                       │
             ┌─────────┴─────────┐
             │                   │
          Request             Event
             │                   │
             │             inngest.send()
             │                   │
             ▼                   ▼
          Response            INNGEST
                                 │
                     ┌───────────┼───────────┐
                     │           │           │
                     ▼           ▼           ▼
                  Function    Function    Function
                     │
                ┌────┴────┐
                │         │
              Step       Step
                │         │
                ▼         ▼
              DB/API   Email/CRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next.js handles your application.&lt;/p&gt;

&lt;p&gt;Inngest handles the durable background execution.&lt;/p&gt;

&lt;p&gt;Your functions describe workflows.&lt;/p&gt;

&lt;p&gt;Steps divide those workflows into retryable units.&lt;/p&gt;

&lt;p&gt;And the dashboard lets you see what actually happened.&lt;/p&gt;




&lt;h1&gt;
  
  
  46. Final checklist
&lt;/h1&gt;

&lt;p&gt;Before calling your Inngest setup complete, check these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;inngest&lt;/code&gt; is installed&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;src/inngest/client.ts&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;[ ] Functions live under &lt;code&gt;src/inngest/functions/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Every function has a stable ID&lt;/li&gt;
&lt;li&gt;[ ] Functions have event triggers&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;/api/inngest&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;serve()&lt;/code&gt; includes all your functions&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;INNGEST_DEV=1&lt;/code&gt; is configured for local development&lt;/li&gt;
&lt;li&gt;[ ] Inngest Dev Server is running&lt;/li&gt;
&lt;li&gt;[ ] You can open &lt;code&gt;localhost:8288&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] You can send a test event&lt;/li&gt;
&lt;li&gt;[ ] The function appears in the Dev Server&lt;/li&gt;
&lt;li&gt;[ ] You can inspect a successful run&lt;/li&gt;
&lt;li&gt;[ ] You can intentionally trigger an error and inspect the failure&lt;/li&gt;
&lt;li&gt;[ ] Important side effects are inside &lt;code&gt;step.run()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Production has an Event Key&lt;/li&gt;
&lt;li&gt;[ ] Production has a Signing Key&lt;/li&gt;
&lt;li&gt;[ ] Secrets aren't committed to Git&lt;/li&gt;
&lt;li&gt;[ ] You have considered idempotency&lt;/li&gt;
&lt;li&gt;[ ] You have considered retries&lt;/li&gt;
&lt;li&gt;[ ] You have a plan for production monitoring&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The nice thing about Inngest is that you don't have to think of it as "another queue system."&lt;/p&gt;

&lt;p&gt;A better way to think about it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Inngest lets you describe reliable background work as code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You publish an event:&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;await&lt;/span&gt; &lt;span class="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A function reacts:&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="nx"&gt;inngest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the actual work is broken into durable steps:&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send-email&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="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;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That simple model scales surprisingly far.&lt;/p&gt;

&lt;p&gt;You can start with one function that sends an email and eventually end up with workflows involving retries, delays, scheduled jobs, external APIs, database operations, concurrency controls, and multiple environments — without having to build the underlying queue and workflow infrastructure yourself.&lt;/p&gt;

&lt;p&gt;The most important thing is not to rush into every advanced Inngest feature.&lt;/p&gt;

&lt;p&gt;Start small.&lt;/p&gt;

&lt;p&gt;Get this working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event
  ↓
Function
  ↓
Step
  ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the Dev Server, break something deliberately, watch the failed run, fix it, and rerun it.&lt;/p&gt;

&lt;p&gt;Once that workflow makes sense, the rest of Inngest becomes considerably easier to understand.&lt;/p&gt;

&lt;p&gt;For the latest SDK details, especially around v4 APIs and deployment configuration, keep the official Inngest documentation nearby because the platform and SDK continue to evolve.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>inngest</category>
    </item>
    <item>
      <title>Stream your movies to VLC across devices with this simple FTP Server with python.</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Wed, 22 Jul 2026 15:10:12 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/stream-your-movies-to-vlc-across-devices-with-this-simple-ftp-server-with-python-29lb</link>
      <guid>https://dev.to/ishanjarwal/stream-your-movies-to-vlc-across-devices-with-this-simple-ftp-server-with-python-29lb</guid>
      <description>&lt;p&gt;Sometimes you just need to move a few files between machines on your local network.&lt;/p&gt;

&lt;p&gt;You don't want to configure a full FTP daemon, create system users, edit configuration files, and set up a permanent service. You just want to share a directory, grab the files from another machine, and shut the server down when you're finished.&lt;/p&gt;

&lt;p&gt;Python makes this surprisingly easy with &lt;strong&gt;pyftpdlib&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike HTTP, Python doesn't ship with a built-in FTP server. If you've used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m http.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you might expect something similar for FTP. There isn't an FTP equivalent in Python's standard library, but &lt;code&gt;pyftpdlib&lt;/code&gt; fills that gap nicely.&lt;/p&gt;

&lt;p&gt;For temporary development environments, home networks, VMs, and quick file transfers, it's often all you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing pyftpdlib on Arch Linux
&lt;/h2&gt;

&lt;p&gt;On Arch Linux, installing packages globally with &lt;code&gt;pip&lt;/code&gt; isn't generally the best idea because the system Python environment is managed by &lt;code&gt;pacman&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two sensible ways to install &lt;code&gt;pyftpdlib&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Install with Pacman
&lt;/h3&gt;

&lt;p&gt;If the package is available in your configured repositories, this is the simplest approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -S python-pyftpdlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, you can invoke it directly through Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2: Use a Virtual Environment
&lt;/h3&gt;

&lt;p&gt;For something temporary—or if you prefer keeping Python packages isolated—a virtual environment works well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m venv .venv
source .venv/bin/activate
pip install pyftpdlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start the server with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you're finished, leave the environment with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Simplest FTP Server
&lt;/h2&gt;

&lt;p&gt;Suppose you're inside a directory containing files you want to share:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pyftpdlib&lt;/code&gt; starts an FTP server and exposes the current working directory.&lt;/p&gt;

&lt;p&gt;You'll see output indicating the address and port the server is listening on.&lt;/p&gt;

&lt;p&gt;By default, this is useful primarily as a quick anonymous FTP server. Anonymous users are restricted to downloading files rather than modifying the directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing a Specific Directory
&lt;/h2&gt;

&lt;p&gt;You don't actually have to &lt;code&gt;cd&lt;/code&gt; into the directory first.&lt;/p&gt;

&lt;p&gt;You can tell &lt;code&gt;pyftpdlib&lt;/code&gt; exactly which directory should become the FTP root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib -d ~/Downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly useful when you're running the command from scripts or another working directory.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib -d ~/Videos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now clients connecting to the FTP server will see the contents of &lt;code&gt;~/Videos&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Port
&lt;/h2&gt;

&lt;p&gt;FTP traditionally uses port &lt;strong&gt;21&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On Linux, however, ports below 1024 normally require elevated privileges. Running an entire temporary Python FTP server as root just to get port 21 isn't a great trade-off.&lt;/p&gt;

&lt;p&gt;Instead, use a higher port such as &lt;code&gt;2121&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib -p 2121
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or combine it with a directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib -p 2121 -d ~/Downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I generally prefer &lt;code&gt;2121&lt;/code&gt; for temporary FTP servers because it makes the purpose obvious while avoiding root privileges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting a Username and Password
&lt;/h2&gt;

&lt;p&gt;Anonymous access is convenient, but you probably don't want everyone on your network accessing the directory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pyftpdlib&lt;/code&gt; lets you specify credentials directly from the command line.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib \
    -u ishu \
    -P 'my-password' \
    -p 2121 \
    -d ~/Downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-u ishu&lt;/code&gt; sets the FTP username.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-P 'my-password'&lt;/code&gt; sets the password.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p 2121&lt;/code&gt; runs the server on port 2121.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d ~/Downloads&lt;/code&gt; makes &lt;code&gt;~/Downloads&lt;/code&gt; the FTP root.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A client would then connect using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: ishu
Password: my-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing worth remembering: putting passwords directly into commands can leave them in your shell history. That's fine for disposable local credentials, but don't reuse an important password here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allowing File Uploads
&lt;/h2&gt;

&lt;p&gt;So far, we're mainly using FTP to download files.&lt;/p&gt;

&lt;p&gt;Sometimes the opposite is more useful. Maybe you want to quickly send files from your phone or another computer back to your Arch machine.&lt;/p&gt;

&lt;p&gt;Enable write access with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combined with authentication and a specific directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib \
    -u ishu \
    -P 'temporary-password' \
    -w \
    -p 2121 \
    -d ~/Downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-w&lt;/code&gt; flag enables write operations, allowing clients to upload and modify files.&lt;/p&gt;

&lt;p&gt;Be more careful with this option. Anyone who obtains the credentials may be able to modify files inside the shared directory.&lt;/p&gt;

&lt;p&gt;For temporary transfers, I like creating a dedicated directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ~/ftp-share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then expose only that directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib \
    -u ishu \
    -P 'temporary-password' \
    -w \
    -p 2121 \
    -d ~/ftp-share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way you're not accidentally exposing your home directory or something more important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Server Reachable from Other Devices
&lt;/h2&gt;

&lt;p&gt;If you're connecting from another computer, binding only to localhost won't help. The server needs to listen on an interface reachable from your network.&lt;/p&gt;

&lt;p&gt;You can explicitly bind it to all interfaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib \
    -i 0.0.0.0 \
    -p 2121 \
    -d ~/ftp-share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With authentication and uploads enabled, a more complete command becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib \
    -i 0.0.0.0 \
    -p 2121 \
    -u ishu \
    -P 'temporary-password' \
    -w \
    -d ~/ftp-share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you need the local IP address of your Arch machine.&lt;/p&gt;

&lt;p&gt;One quick way to find it is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Suppose your machine is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Your FTP server would then be reachable at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ftp://192.168.1.42:2121
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from other devices on the same network, assuming your firewall and network configuration allow the connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting with curl
&lt;/h2&gt;

&lt;p&gt;You don't necessarily need a graphical FTP client.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; works perfectly well for quick transfers.&lt;/p&gt;

&lt;p&gt;List the files available on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl ftp://192.168.1.42:2121/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -u ishu ftp://192.168.1.42:2121/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; will prompt for the password instead of forcing you to put it directly in the command.&lt;/p&gt;

&lt;p&gt;Download a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -u ishu \
    ftp://192.168.1.42:2121/example.zip \
    -o example.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If write access is enabled, upload a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -u ishu \
    -T photo.jpg \
    ftp://192.168.1.42:2121/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting with an FTP Client
&lt;/h2&gt;

&lt;p&gt;You can also use a traditional command-line FTP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ftp 192.168.1.42 2121
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the username and password when prompted.&lt;/p&gt;

&lt;p&gt;Graphical clients such as FileZilla work as well. You'd configure roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host: 192.168.1.42
Port: 2121
Protocol: FTP
Username: ishu
Password: your temporary password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Command Worth Remembering
&lt;/h2&gt;

&lt;p&gt;For a temporary authenticated FTP drop folder, the command I find most useful is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pyftpdlib \
    -i 0.0.0.0 \
    -p 2121 \
    -u ishu \
    -P 'temporary-password' \
    -w \
    -d ~/ftp-share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gives you a specific shared directory, a non-privileged port, authentication, upload support, and access from other devices on your network.&lt;/p&gt;

&lt;p&gt;Once the transfer is finished, press:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl+C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the server disappears.&lt;/p&gt;

&lt;p&gt;No daemon to disable. No service left running in the background. No permanent FTP configuration to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Security Notes
&lt;/h2&gt;

&lt;p&gt;This setup is best thought of as a &lt;strong&gt;temporary file-transfer tool&lt;/strong&gt;, particularly on a trusted local network.&lt;/p&gt;

&lt;p&gt;Traditional FTP does not encrypt credentials or file transfers. Don't expose this server directly to the public internet, and don't use a password that you use elsewhere.&lt;/p&gt;

&lt;p&gt;For transfers across untrusted networks, SSH/SFTP is usually the better choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sftp username@server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all you need is downloading files without authentication or uploads, Python's built-in HTTP server may be even simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m http.server 8000 --directory ~/ftp-share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting thing about &lt;code&gt;pyftpdlib&lt;/code&gt; isn't that it replaces a proper FTP server. It doesn't need to.&lt;/p&gt;

&lt;p&gt;Its value is that when you need an FTP server &lt;strong&gt;right now&lt;/strong&gt;, for one directory and perhaps only ten minutes, you can have one running before you'd normally finish editing the configuration file for a traditional FTP daemon.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ftpserver</category>
      <category>ftp</category>
    </item>
    <item>
      <title>Using Ventoy to create bootable device with multiple OS disk images.</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Tue, 02 Jun 2026 07:53:31 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/using-ventoy-to-create-bootable-device-with-multiple-os-disk-images-43ac</link>
      <guid>https://dev.to/ishanjarwal/using-ventoy-to-create-bootable-device-with-multiple-os-disk-images-43ac</guid>
      <description>&lt;p&gt;Ventoy lets you create a multi-boot USB drive where you can simply copy ISO files directly (Windows, Linux, etc.) without reformatting or re-burning each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Ventoy in Arch Linux
&lt;/h2&gt;

&lt;p&gt;You can install it via the &lt;strong&gt;AUR&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yay -S ventoy-bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you don’t have &lt;/em&gt;&lt;code&gt;yay&lt;/code&gt;&lt;em&gt;, install it or use another AUR helper like &lt;/em&gt;&lt;code&gt;paru&lt;/code&gt;&lt;em&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, you can manually download it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://aur.archlinux.org/ventoy-bin.git
cd ventoy-bin
makepkg -si
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Identify Your USB Drive
&lt;/h2&gt;

&lt;p&gt;Plug in your USB stick and list your disks:&lt;br&gt;
&lt;/p&gt;

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

// Shows something like this 

sda    500G  HDD
sdb    16G   USB Drive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;So here the USB is &lt;code&gt;/dev/sdb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install Ventoy on the USB
&lt;/h2&gt;

&lt;p&gt;Run Ventoy to install the bootloader on the USB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ventoy -i /dev/sdX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;You’ll get a confirmation prompt, type &lt;code&gt;y&lt;/code&gt; to proceed.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;To &lt;strong&gt;update&lt;/strong&gt; Ventoy in the future without erasing files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ventoy -u /dev/sdX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add ISO Images
&lt;/h2&gt;

&lt;p&gt;Once installed, Ventoy creates two partitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A small Ventoy system partition&lt;/li&gt;
&lt;li&gt;A large &lt;strong&gt;exFAT&lt;/strong&gt; partition (visible in file manager)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can now &lt;strong&gt;mount the USB&lt;/strong&gt; and &lt;strong&gt;copy any &lt;/strong&gt;&lt;code&gt;.iso&lt;/code&gt;&lt;strong&gt;, &lt;/strong&gt;&lt;code&gt;.img&lt;/code&gt;&lt;strong&gt;, or &lt;/strong&gt;&lt;code&gt;.wim&lt;/code&gt; files directly into it, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp ~/Downloads/archlinux.iso /run/media/$USER/Ventoy/
cp ~/Downloads/ubuntu.iso /run/media/$USER/Ventoy/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Boot from Ventoy USB
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reboot your system.&lt;/li&gt;
&lt;li&gt;Enter your &lt;strong&gt;boot menu&lt;/strong&gt; (usually &lt;code&gt;F12&lt;/code&gt;, &lt;code&gt;Esc&lt;/code&gt;, or &lt;code&gt;F8&lt;/code&gt; depending on your BIOS/UEFI).&lt;/li&gt;
&lt;li&gt;Select your Ventoy USB device.&lt;/li&gt;
&lt;li&gt;Ventoy menu will appear with a list of all ISO images on the USB.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use arrow keys to choose the OS you want to boot and hit &lt;strong&gt;Enter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistence (for live Linux distros):&lt;/strong&gt;
You can create a persistence file and attach it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  sudo ventoy -c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure Boot Support:&lt;/strong&gt;
Ventoy supports Secure Boot, you can enable it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  sudo ventoy -g /dev/sdX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can rename ISOs for clarity — Ventoy reads their labels dynamically.&lt;/li&gt;
&lt;li&gt;If you add or remove ISOs, you don’t need to “refresh” anything — just copy them in/out.&lt;/li&gt;
&lt;li&gt;Works perfectly with both &lt;strong&gt;BIOS&lt;/strong&gt; and &lt;strong&gt;UEFI&lt;/strong&gt; boot modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

</description>
      <category>archlinux</category>
      <category>linux</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to download Youtube Videos &amp; Playlists with yt-dlp (100% Free)</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Tue, 02 Jun 2026 07:51:54 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/how-to-download-youtube-videos-playlists-with-yt-dlp-100-free-1d1k</link>
      <guid>https://dev.to/ishanjarwal/how-to-download-youtube-videos-playlists-with-yt-dlp-100-free-1d1k</guid>
      <description>&lt;p&gt;This is a short tutorial on how to download an entire youtube playlist with just one command line with yt-dlp, an open source package, No ads, No subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 : Install yt-dlp
&lt;/h2&gt;

&lt;p&gt;To install the package, use wget&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -O yt-dlp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Make it executable and move to PATH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod +x yt-dlp
sudo mv yt-dlp /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the installation :
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yt-dlp --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install ffmpeg (needed for mp3 conversion)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -S ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Download with a single line :
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Option / Format&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Command Example&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Download video (default, best quality)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Download best video + audio (merge mp4/mkv)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -f "bestvideo+bestaudio" &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extract audio only (default format)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -x &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extract audio as MP3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -x --audio-format mp3 &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extract audio as WAV&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -x --audio-format wav &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Download entire playlist as MP3 (with track numbers)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -x --audio-format mp3 -o "%(playlist_index)02d - %(title)s.%(ext)s" &amp;lt;PLAYLIST_URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Save inside folder named after playlist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -x --audio-format mp3 -o "%(playlist_title)s/%(playlist_index)02d - %(title)s.%(ext)s" &amp;lt;PLAYLIST_URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Download specific items from playlist (e.g. 1–10, 15, 20)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp --playlist-items 1-10,15,20 &amp;lt;PLAYLIST_URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Download subtitles (if available)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp --write-subs --sub-lang en &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Embed metadata (artist, album, thumbnail, etc.) into MP3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -x --audio-format mp3 --embed-metadata --embed-thumbnail &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Set custom output filename&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yt-dlp -o "%(title)s.%(ext)s" &amp;lt;URL&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;And that's a wrap&lt;/p&gt;

</description>
      <category>archlinux</category>
      <category>cli</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Abandoned project came alive | I built and shipped my first SaaS</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Sat, 30 May 2026 18:35:01 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/abandoned-project-came-alive-i-built-and-shipped-my-first-saas-3b3m</link>
      <guid>https://dev.to/ishanjarwal/abandoned-project-came-alive-i-built-and-shipped-my-first-saas-3b3m</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  I Finally Finished the Side Project That Refused to Die
&lt;/h1&gt;

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

&lt;p&gt;Tonight, I finally deployed a project that has been sitting in my "I'll finish it someday" folder for far too long.&lt;/p&gt;

&lt;p&gt;It's called Resumazing, an AI-powered Resume Builder SaaS that helps users create professional resumes with AI assistance, ATS analysis, resume scoring, job fit analysis and multiple resume management features.&lt;/p&gt;

&lt;p&gt;What started as a weekend experiment slowly turned into a real product. Every time I thought it was almost done, I found another feature to build, another bug to fix, or another UI improvement to make.&lt;/p&gt;

&lt;p&gt;At several points, I considered abandoning it and moving on to the next shiny idea. But this I got the push I needed to finally cross the finish line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://resumazing.vercel.app" rel="noopener noreferrer"&gt;https://resumazing.vercel.app&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;This project wasn't abandoned because it was broken.&lt;br&gt;
It was abandoned because it was &lt;em&gt;almost finished&lt;/em&gt;.&lt;br&gt;
And honestly, that's the hardest stage.&lt;/p&gt;

&lt;p&gt;The core functionality already worked months ago, but there were dozens of unfinished details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication improvements&lt;/li&gt;
&lt;li&gt;Resume templating issues&lt;/li&gt;
&lt;li&gt;Better validation and error handling&lt;/li&gt;
&lt;li&gt;UI polish&lt;/li&gt;
&lt;li&gt;Performance fixes&lt;/li&gt;
&lt;li&gt;Deployment setup&lt;/li&gt;
&lt;li&gt;Countless small bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project sat untouched while I learned new technologies, started new ideas, and kept telling myself I'd come back to it later.&lt;/p&gt;

&lt;p&gt;This week, I finally did.&lt;/p&gt;

&lt;p&gt;I stopped adding features, focused on completing what already existed, and pushed through the final stretch.&lt;/p&gt;

&lt;p&gt;A few hours later, the deployment was live.&lt;br&gt;
Not perfect. Not finished forever.&lt;br&gt;
But finished enough to be used by real people.&lt;/p&gt;

&lt;p&gt;And that's a much better place for a project than sitting in a local folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech I used
&lt;/h2&gt;

&lt;p&gt;I built it on nextjs with industry best practices like precommit scripts with husky, ensuring typesafety all over, writing maintainable code. &lt;br&gt;
I used the follwing : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inngest - for background jobs&lt;/li&gt;
&lt;li&gt;dodopayments - for payments and subscription lifecycle handling&lt;/li&gt;
&lt;li&gt;@react-pdf/renderer - for making templates&lt;/li&gt;
&lt;li&gt;zustand - for state management&lt;/li&gt;
&lt;li&gt;zod - for validations&lt;/li&gt;
&lt;li&gt;cloudinary - for object storage&lt;/li&gt;
&lt;li&gt;react-hook-forms - for form state handling&lt;/li&gt;
&lt;li&gt;and many more packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find the project here : &lt;a href="https://github.com/ishanjarwal/resume-builder-saas" rel="noopener noreferrer"&gt;https://github.com/ishanjarwal/resume-builder-saas&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot was surprisingly helpful during the finishing phase.&lt;/p&gt;

&lt;p&gt;The hardest part wasn't building new features, it was dealing with all the small tasks that come with polishing a project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refactoring code&lt;/li&gt;
&lt;li&gt;Generating repetitive boilerplate&lt;/li&gt;
&lt;li&gt;Fixing type issues&lt;/li&gt;
&lt;li&gt;Writing validation logic&lt;/li&gt;
&lt;li&gt;Explaining unfamiliar APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For me, Copilot worked best as a development partner rather than an autopilot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Starting projects is exciting.&lt;br&gt;
Finishing them is difficult.&lt;/p&gt;

&lt;p&gt;This project taught me that most side projects don't fail because the idea is bad—they fail because the final 10% takes longer than the first 90%.&lt;/p&gt;

&lt;p&gt;Tonight, I finally shipped mine.&lt;br&gt;
And honestly, that feels pretty good...&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
    </item>
    <item>
      <title>Installing PostgreSQL on Arch Linux | Practical Setup Guide</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Sat, 30 May 2026 17:57:25 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/installing-postgresql-on-arch-linux-practical-setup-guide-1hme</link>
      <guid>https://dev.to/ishanjarwal/installing-postgresql-on-arch-linux-practical-setup-guide-1hme</guid>
      <description>&lt;p&gt;Installing postgres on windows/mac is pretty simple, you just follow the steps on the installation wizard and you get it working. You will also find lots of video tutorials and blogs regarding installation in windows/mac.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;But when it comes to linux (specifically arch), things get interesting. Since linux gives you more control, setting up anything on it can seem tricky at first glance but as you go through, you understand each and every step and its purpose.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Before getting into it, understand this : Postgres is not like MongoDB which you initialize once through the CLI by providing the --dbpath flag, it is a system service. Meaning it will run on the system 24x7 (if we configure it that way) and different system users will be able to use it according to the access they have been granted.&lt;/p&gt;




&lt;h2&gt;
  
  
  First things first : Installation
&lt;/h2&gt;

&lt;p&gt;Always update the packages first :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -Syu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -S postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lets initialize a Database Cluster
&lt;/h2&gt;

&lt;p&gt;PostgreSQL requires a data directory initialization. This is the directory which holds the actual data (one-time step unlike MongoDB) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo -u postgres initdb -D /var/lib/postgres/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Flags explained&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-u postgres&lt;/code&gt; → run command as postgres system user &lt;code&gt;initdb&lt;/code&gt; → creates system tables &amp;amp; internal structure &lt;code&gt;-D&lt;/code&gt; → data directory location&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start &amp;amp; Enable Service
&lt;/h2&gt;

&lt;p&gt;Start server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl start postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable on every boot :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemctl status postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter psql Shell
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is psql ?&lt;/strong&gt; It is a command line interface to interact with out postgres service, databases, relations etc.&lt;/p&gt;

&lt;p&gt;Lets enter the psql shell with the user “postgres”.&amp;nbsp;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On installing postgres, a new user named “postgres” is generated with default privileges.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo -u postgres psql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why no password?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default auth configuration is the “peer” auth. Peer auth uses our OS user to login&lt;/li&gt;
&lt;li&gt;OS user identity is trusted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Role for a Project
&lt;/h2&gt;

&lt;p&gt;It is always a good practice to create a separate role for every major project. This way you follow modular principles.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;psql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE ROLE myapp WITH
  LOGIN
  PASSWORD 'devpass123'
  CREATEDB;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LOGIN → allows authentication&lt;/li&gt;
&lt;li&gt;PASSWORD → required for apps/ORMs&lt;/li&gt;
&lt;li&gt;CREATEDB → grant access to create databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE DATABASE myapp_db OWNER myapp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ownership = full control over that DB.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some frequently used and useful psql Commands
&lt;/h2&gt;

&lt;p&gt;List Databases&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;List roles (users)&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;List tables/relations&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Show current connection details&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Show the active role&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connection URL Format
&lt;/h2&gt;

&lt;p&gt;To connect to the database, you will need a url (Yes this is the one you add in your environment variables)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General structure&lt;/strong&gt; : postgresql://USER:PASSWORD@HOST:PORT/DATABASE&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; : postgresql://myapp:devpass123@localhost:5432/myapp_db &lt;/p&gt;

&lt;p&gt;This url can be used by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prisma&lt;/li&gt;
&lt;li&gt;ORMs&lt;/li&gt;
&lt;li&gt;Drivers&lt;/li&gt;
&lt;li&gt;CLI tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable Password Authentication (Critical for Prisma)
&lt;/h2&gt;

&lt;p&gt;Edit config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /var/lib/postgres/data/pg_hba.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change these lines according to your needs :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local   all   all                 md5
host    all   all   127.0.0.1/32  md5
host    all   all   ::1/128       md5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace md5 with one of the following :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trust&lt;/code&gt; → no password&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peer&lt;/code&gt; → OS identity&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;md5&lt;/code&gt; / &lt;code&gt;scram-sha-256&lt;/code&gt; → password-based login&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Restart PostgreSQL
&lt;/h3&gt;

&lt;p&gt;To apply the config changes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing our Connection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql "postgresql://myapp:devpass123@localhost:5432/myapp_db"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -U myapp -d myapp_db -h localhost -W
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Flags explained&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-U&lt;/code&gt; → database role &lt;code&gt;-d&lt;/code&gt; → database name &lt;code&gt;-h&lt;/code&gt; → host (forces TCP) &lt;code&gt;-W&lt;/code&gt; → force password prompt&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Common Errors &amp;amp; Causes you might face
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;password authentication failed&lt;/em&gt; → wrong password&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;database does not exist&lt;/em&gt; → DB missing&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;role does not exist&lt;/em&gt; → user missing&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;could not connect to server&lt;/em&gt; → service stopped / wrong port&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Again, remember&lt;/p&gt;

&lt;p&gt;PostgreSQL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always-running service&lt;/li&gt;
&lt;li&gt;Config-driven behavior&lt;/li&gt;
&lt;li&gt;URL = connection info only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No manual server start like MongoDB.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Happy building 🚀&lt;/p&gt;

</description>
      <category>postgressql</category>
      <category>archlinux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Git Rebase vs Reset vs Revert | When to use What ?</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Fri, 29 May 2026 12:54:04 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/git-rebase-vs-reset-vs-revert-when-to-use-what--28kp</link>
      <guid>https://dev.to/ishanjarwal/git-rebase-vs-reset-vs-revert-when-to-use-what--28kp</guid>
      <description>&lt;h3&gt;
  
  
  The Scenario: "Oops, I Made a Messy Commit History"
&lt;/h3&gt;

&lt;p&gt;Imagine you're working on a feature branch called &lt;code&gt;feature/login-page&lt;/code&gt;. You’ve made a few commits, but now :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One of the commits has a &lt;strong&gt;bug&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Another commit has a &lt;strong&gt;wrong commit message&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Your commit history looks like a &lt;strong&gt;jumbled mess&lt;/strong&gt; compared to &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Time to clean it up! But… should you &lt;strong&gt;rebase&lt;/strong&gt;, &lt;strong&gt;reset&lt;/strong&gt;, or &lt;strong&gt;revert&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  First, Let’s Understand the Core Idea of Each:
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it Does&lt;/th&gt;
&lt;th&gt;Safe for Shared Branches?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rebase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rewrites commit history to make it linear and clean&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt;, unless used carefully&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Moves the HEAD and branch pointer to a different commit&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt;, dangerous on shared branches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;revert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a new commit that undoes a previous commit&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt;, safe to fix public history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Git Rebase&lt;/strong&gt; : "Let’s Rewrite History Neatly"
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What is it?
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt; allows you to move or "replay" your commits onto another branch or rearrange them to make a clean, linear history.&lt;/p&gt;

&lt;h4&gt;
  
  
  When to Use?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You want to &lt;strong&gt;clean up messy commit history&lt;/strong&gt; before merging.&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;squash commits&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;change commit messages&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example:
&lt;/h4&gt;

&lt;p&gt;You have these commits in &lt;code&gt;feature/login-page&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A — B — C — D  (feature/login-page)         
        ↑       
      Buggy commit (C) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to fix commit &lt;code&gt;C&lt;/code&gt; and clean up the message in &lt;code&gt;D&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution: Interactive Rebase
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git rebase -i HEAD~3 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick abc123 B pick def456 C pick ghi789 D 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change "pick" to "edit"&lt;/strong&gt; for C to fix the bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change "pick" to "reword"&lt;/strong&gt; for D to fix the commit message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squash commits&lt;/strong&gt; if needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Important:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This rewrites commit hashes.&lt;/li&gt;
&lt;li&gt;If your branch is &lt;strong&gt;pushed and shared&lt;/strong&gt;, rebasing can mess up others' history. Use with care!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Git Reset&lt;/strong&gt; : "Let’s Go Back in Time (Dangerously Powerful)"
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What is it?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;git reset&lt;/code&gt; moves your branch pointer (&lt;code&gt;HEAD&lt;/code&gt;) to a previous commit. It can &lt;strong&gt;unstage files&lt;/strong&gt;, &lt;strong&gt;remove commits&lt;/strong&gt;, or even &lt;strong&gt;delete code changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h4&gt;
  
  
  Types of Reset:
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What happens?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --soft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keeps your changes staged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --mixed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unstages changes but keeps them in the working directory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --hard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Deletes&lt;/strong&gt; your changes completely. Dangerous!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h4&gt;
  
  
  When to Use?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You made &lt;strong&gt;local commits&lt;/strong&gt; you don’t want anymore.&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;uncommit but keep the changes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h4&gt;
  
  
  Example:
&lt;/h4&gt;

&lt;p&gt;You made two useless commits on top of &lt;code&gt;main&lt;/code&gt; and want to remove them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A — B — C — D  (main)              
            ↑            
            Your branch (HEAD) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To undo D and C:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --soft HEAD~2 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Your changes are still there but &lt;strong&gt;unstaged (due to --soft flag)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If you want to kill the changes, do
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --hard HEAD~2 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that your git timeline will look like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A — B  (main)
    ↑            
    Your branch (HEAD) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h4&gt;
  
  
  Important:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never reset a branch that is already pushed and shared with others&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Resetting can &lt;strong&gt;permanently lose work&lt;/strong&gt; (especially &lt;code&gt;--hard&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Git Revert&lt;/strong&gt; : "The Safe Undo Button"
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What is it?
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;git revert&lt;/code&gt; creates a &lt;strong&gt;new commit&lt;/strong&gt; that undoes the changes of a previous commit. It &lt;strong&gt;preserves history&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  When to Use?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You want to &lt;strong&gt;undo a bad commit in a shared branch&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;keep history intact but fix a mistake&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h4&gt;
  
  
  Example:
&lt;/h4&gt;

&lt;p&gt;Commit &lt;code&gt;C&lt;/code&gt; introduced a bug. But you’ve already pushed it to &lt;code&gt;origin/main&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A — B — C — D  (main)           
             ↑            
            Bug lives here 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can revert C safely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git revert &amp;lt;commit-hash-of-C&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git will create a new commit :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A — B — C — D — E  (main)                    
                ↑                 
              "Revert C" 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h4&gt;
  
  
  Important:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safe for shared/public branches&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Doesn’t remove the commit but &lt;strong&gt;inverts its changes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Summary: When to Use What?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Use When…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git rebase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You want to &lt;strong&gt;rewrite and clean history&lt;/strong&gt; (local-only).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You want to &lt;strong&gt;remove commits&lt;/strong&gt; or &lt;strong&gt;unstage changes&lt;/strong&gt; locally.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git revert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You need to &lt;strong&gt;undo commits safely in shared branches&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Visual Cheat Sheet
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"I messed up a commit and want to fix it before pushing" → &lt;strong&gt;Rebase / Reset (local only)&lt;/strong&gt;&lt;br&gt;
"I pushed a bad commit, now I need to undo it on remote" → &lt;strong&gt;Revert&lt;/strong&gt;&lt;br&gt;
"I made several ugly small commits, want to squash them" → &lt;strong&gt;Rebase -i&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Final Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never rebase or reset public history unless you know what you’re doing.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;git log --oneline --graph&lt;/code&gt; to visualize the history before making changes.&lt;/li&gt;
&lt;li&gt;Always backup your branch with &lt;code&gt;git branch backup/my-branch&lt;/code&gt; before risky operations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-rebase" rel="noopener noreferrer"&gt;Git Rebase Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-reset" rel="noopener noreferrer"&gt;Git Reset Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-revert" rel="noopener noreferrer"&gt;Git Revert Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>devops</category>
    </item>
    <item>
      <title>Learn Package Management on Arch Linux: pacman &amp; yay</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Fri, 29 May 2026 07:59:48 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/learn-package-management-on-arch-linux-pacman-yay-267j</link>
      <guid>https://dev.to/ishanjarwal/learn-package-management-on-arch-linux-pacman-yay-267j</guid>
      <description>&lt;h1&gt;
  
  
  Part 1: Understanding &lt;code&gt;pacman&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;pacman&lt;/code&gt; is the default package manager for Arch Linux. It directly interacts with the official repositories and handles installation, updates, and removal of software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Syntax
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="o"&gt;[&lt;/span&gt;options] &lt;span class="o"&gt;[&lt;/span&gt;package_name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common pacman Commands
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Update package database and system&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo pacman -Syu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sync package list (&lt;code&gt;-Sy&lt;/code&gt;) and upgrade all packages (&lt;code&gt;-u&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Install a package&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo pacman -S &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Installs package from official repo.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Remove a package&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo pacman -R &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes a package (keeps dependencies).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Remove a package and its unused dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo pacman -Rns &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clean uninstall — removes package, configs, and dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search for a package&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pacman -Ss &amp;lt;keyword&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search available packages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;List installed packages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pacman -Q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows all installed packages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Show package info&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pacman -Qi &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Displays metadata (version, size, dependencies).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clean cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo pacman -Sc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes old packages from cache.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Official Reference:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://wiki.archlinux.org/title/Pacman" rel="noopener noreferrer"&gt;Pacman Manual - ArchWiki&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🧹 Pro Tip
&lt;/h3&gt;

&lt;p&gt;To free up space, remove &lt;em&gt;unused dependencies&lt;/em&gt; and &lt;em&gt;old cached packages&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-Rns&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pacman &lt;span class="nt"&gt;-Qdtq&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-Scc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Part 2: Using &lt;code&gt;yay&lt;/code&gt; for AUR Packages
&lt;/h1&gt;

&lt;p&gt;The &lt;strong&gt;Arch User Repository (AUR)&lt;/strong&gt; contains community-maintained build scripts (PKGBUILDs) for software not in the official repositories.&lt;br&gt;&lt;br&gt;
&lt;code&gt;yay&lt;/code&gt; (Yet Another Yaourt) is a lightweight, fast, and user-friendly &lt;strong&gt;AUR helper&lt;/strong&gt; that automates the build and installation process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Syntax
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yay &lt;span class="o"&gt;[&lt;/span&gt;options] &lt;span class="o"&gt;[&lt;/span&gt;package_name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common yay Commands
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Update everything (official + AUR)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -Syu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sync and upgrade both official and AUR packages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Install an AUR package&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -S &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatically fetches, builds, and installs from AUR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search for AUR packages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -Ss &amp;lt;keyword&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search both official and AUR repositories.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Remove a package&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -Rns &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same as pacman — removes package + unused dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clean build cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -Sc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clean yay’s build directory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;List all AUR packages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -Qm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows all manually installed (AUR) packages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check for outdated AUR packages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yay -Qua&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lists AUR updates available.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Unlike pacman, &lt;code&gt;yay&lt;/code&gt; doesn’t need &lt;code&gt;sudo&lt;/code&gt; for most operations until the build process actually installs system files — it will prompt automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official Reference:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Jguer/yay" rel="noopener noreferrer"&gt;Yay GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Flag Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman/yay&lt;/td&gt;
&lt;td&gt;Install package(s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman/yay&lt;/td&gt;
&lt;td&gt;Remove package(s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Ss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman/yay&lt;/td&gt;
&lt;td&gt;Search package in repos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Qi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman&lt;/td&gt;
&lt;td&gt;Show package info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Qm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yay&lt;/td&gt;
&lt;td&gt;List manually installed (AUR) packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Syu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman/yay&lt;/td&gt;
&lt;td&gt;Sync database and upgrade all packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Rns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman/yay&lt;/td&gt;
&lt;td&gt;Remove package + dependencies + configs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Sc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman/yay&lt;/td&gt;
&lt;td&gt;Clean package cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Qdtq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pacman&lt;/td&gt;
&lt;td&gt;List orphaned packages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Further Reading:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Pacman/Tips_and_tricks" rel="noopener noreferrer"&gt;ArchWiki: Pacman Tips &amp;amp; Tricks&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/AUR_helpers" rel="noopener noreferrer"&gt;ArchWiki: AUR Helpers&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>archlinux</category>
      <category>linux</category>
      <category>pacman</category>
      <category>aur</category>
    </item>
    <item>
      <title>A Comprehensive and Comfy Guide to Git.</title>
      <dc:creator>Ishan Jarwal</dc:creator>
      <pubDate>Fri, 29 May 2026 07:47:15 +0000</pubDate>
      <link>https://dev.to/ishanjarwal/a-comprehensive-and-comfy-guide-to-git-39nd</link>
      <guid>https://dev.to/ishanjarwal/a-comprehensive-and-comfy-guide-to-git-39nd</guid>
      <description>&lt;p&gt;Git is not just a tool; it’s a life-saver for developers. It helps you track changes, collaborate with others, and never lose your work. This guide is designed to be a comfy walkthrough of Git , not too heavy on jargon, but packed with useful knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tiny History of Git and Why You Should Care
&lt;/h2&gt;

&lt;p&gt;Git was created by Linus Torvalds in 2005, the same genius who built Linux. It was born out of necessity when the Linux community needed a version control system that was fast, distributed, and free.&lt;/p&gt;

&lt;p&gt;Today, Git powers everything from hobby projects to massive codebases like those at Google, Microsoft, and Facebook. Whether you're working on a personal website or contributing to open-source, Git is the industry standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where is Git
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Software Development (obviously!)&lt;/li&gt;
&lt;li&gt;Documentation Projects&lt;/li&gt;
&lt;li&gt;Collaborative Design Workflows&lt;/li&gt;
&lt;li&gt;Versioning for Configuration Files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it involves files that change over time, Git can manage it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Git Terminologies
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u0br42cumcl4xgwx8e1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u0br42cumcl4xgwx8e1.jpg" width="649" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository (Repo)&lt;/strong&gt;&lt;br&gt;
A directory where your project's Git history is stored. It's like a folder, but smarter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Staging Area&lt;/strong&gt;&lt;br&gt;
Think of this as a "waiting room" where changes sit before you officially save them in the project history.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt;&lt;br&gt;
A snapshot of your project at a specific point in time. This is how you save your work in Git.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push&lt;/strong&gt;&lt;br&gt;
You "push" commits to a remote repository (like GitHub) so others can see your work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Remote&lt;/strong&gt;&lt;br&gt;
A version of your project that's hosted on the internet or another network.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up Git for the First Time
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install Git&lt;/strong&gt;&lt;br&gt;
Download from &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;git-scm.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure Your Identity&lt;/strong&gt;&lt;br&gt;
Before you do anything, tell Git who you are. We do this by telling git what is our &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.email &lt;span class="s2"&gt;"your.email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize a Git Repository&lt;/strong&gt;
Navigate to your project folder and run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your/project&lt;span class="s1"&gt;'s/root
git init
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;.git&lt;/code&gt; folder in your project. If you don't know, folders starting with a "." are hidden folders so you might not see it in the folder tree. This is where the information about your codebase is saved.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tracking Changes (Staging &amp;amp; Committing)
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check Project Status&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows which files have been changed, which are staged, and which are not. You will see something like this in your terminal.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Add Files to Staging Area
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add filename.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one step before committing your changes. Note that the files added to the staging area are the only ones going to be committed. All other files will be "unstaged files"&lt;/p&gt;

&lt;h2&gt;
  
  
  Committing the Changes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add feature X"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git expects you to associate every commit with a message. After running this command, a snapshot of your staged changes will be saved as a "commit".&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing Commit History
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows all commits in your repo. Use &lt;code&gt;q&lt;/code&gt; to exit log view.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;HEAD&lt;/strong&gt; in Git points to the current snapshot (commit) you are working on. Think of it as "where you are" in your project history.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Understanding Branches
&lt;/h1&gt;

&lt;p&gt;A &lt;strong&gt;branch&lt;/strong&gt; in Git is like creating an alternate universe for your code where you can try out new features without disturbing the main project. I would call this the most amazing feature offered by Git. Think of it as you are working on a feature and committing the changes step by step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eg : Initialize Project -&amp;gt; create homepage -&amp;gt; add images -&amp;gt; change fonts . . .&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But parallel to this workflow, you also want to create a new feature say "Authentication" in the same project and without disturbing the main flow.&lt;/p&gt;

&lt;p&gt;When you first initialize your project, the default branch you work on is either "master" or "main"&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a New Branch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch new-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Switch to That Branch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout new-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Or Do Both in One Command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; new-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List All Branches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete a Branch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;-d&lt;/span&gt; old-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can learn more about "Merging Branches" from here. It is an advanced git concept, hence it will not be discussed here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Push Your Code to GitHub
&lt;/h1&gt;

&lt;p&gt;Till now, we have created our repo, some commits (our checkpoints) and also branches. But all of this resides in our local machine. If anything goes wrong your pc, all of your data will be lost. For this purpose we have "Github", a place where you can upload your code so that it stays safe.&lt;/p&gt;

&lt;p&gt;Following theses steps you can do it easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Github account and the repo.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;First step is to create your &lt;a href="https://github.com/signup" rel="noopener noreferrer"&gt;github accout&lt;/a&gt;. Simply add your details, verify your account and your are good to go.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now you can click the "Create New Repository" button, give it a name, make it public/private according to you and hit create.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now your remote repository is created and we need to connect it to our local git project, to do that, follow these steps&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5jholbr2zfza88g8xp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5jholbr2zfza88g8xp1.png" width="799" height="401"&gt;&lt;/a&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5by8tcrdqc16e3su1qj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5by8tcrdqc16e3su1qj.png" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Remote URL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin https://github.com/yourusername/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, origin is nothing but an alias for your remote repository, you can see all the aliases by this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Push to Remote
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will "push" or upload our codebase to our remote repository. The "-u" flag tells Git to set the remote branch (&lt;code&gt;origin/main&lt;/code&gt;) as the upstream (or default) branch for your local branch (&lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Congrats, your code is now safe on github.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bonus Tips: Git in VSCode
&lt;/h1&gt;

&lt;p&gt;If you prefer clicking buttons over typing commands, &lt;strong&gt;Visual Studio Code&lt;/strong&gt; has an excellent Git GUI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Must-have VSCode Git Extensions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  GitLens: Supercharge your Git experience.&lt;/li&gt;
&lt;li&gt;  Git Graph: Visualize your branches and commits.&lt;/li&gt;
&lt;li&gt;  GitHub Pull Requests and Issues: Manage PRs from within VSCode.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

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

&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;This guide covers the comfy essentials to get you started. In the next post, we'll explore advanced Git concepts like merge conflicts, rebasing, reverting commits, and more.&lt;/p&gt;

&lt;p&gt;For now, get your hands dirty and start committing!&lt;/p&gt;

&lt;p&gt;That's it! You're now Git-ready.&lt;/p&gt;

</description>
      <category>git</category>
      <category>versioning</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
