<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Startups - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Startups - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 16:31:48 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/startups/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How I Built a Custom Video Conferencing App with Stream and Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Building full-stack apps can be tough. You have to think about frontend, APIs, databases, auth – plus you have to know how all of these things work together. And building a project like a video conferencing app from scratch can feel even more overwhe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-built-a-custom-video-conferencing-app-with-stream-and-nextjs/</link>
                <guid isPermaLink="false">66fd86ff9cea0a9dc9177283</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 17:46:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727433361539/498f0742-2ff1-4762-b268-2c25eb22017e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building full-stack apps can be tough. You have to think about frontend, APIs, databases, auth – plus you have to know how all of these things work together.</p>
<p>And building a project like a video conferencing app from scratch can feel even more overwhelming, especially with the complexities of managing video streams, user auth, and real-time interactions.</p>
<p>But what if I told you there’s an easier way to do this – one that lets you build your video conferencing app in a fraction of the time?</p>
<p>In this article, I’ll show you how I built a video conferencing app using <a target="_blank" href="https://getstream.io/">Stream</a> and Clerk in Next.js.</p>
<p>Here is the <a target="_blank" href="https://github.com/tyaga001/facetime-on-stream">source code</a> (remember to give it a star ⭐).</p>
<p>Before we start, let me tell you why I wrote this tutorial.</p>
<p>I’m a Software Engineer who cares about writing and I <strong>love</strong> to <strong>code</strong>, <strong>design</strong>, <strong>develop</strong>, and then <strong>teach</strong> people.</p>
<p>I've been using open-source projects, products, and services for a while now, and contributing to many of them to improve them how I can. Last month I built an open-source blog for “awesome developer tools“ called - <a target="_blank" href="https://www.devtoolsacademy.com/">devtoolsacademy</a></p>
<p><a target="_blank" href="https://www.devtoolsacademy.com/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727430858395/70ffbec4-69ab-4f31-a9cb-02b44066ac6b.png" alt="devtoolsacademy" class="image--center mx-auto" width="3072" height="1830" loading="lazy"></a></p>
<p>This article is about sharing the experience I’ve had using yet another awesome developer tool.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-stream">What is Stream?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-app-interface-with-nextjs">How to Build the App Interface with Next.js</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-create-link-modal">The Create Link Modal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-instant-meeting-modal">The Instant Meeting Modal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-join-meeting-modal">The Join Meeting Modal</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-stream-in-a-nextjs-app">How to Set Up Stream in a Next.js app</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-join-calls-with-stream">How to Create and Join Calls with Stream</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-creating-and-scheduling-calls">Creating and Scheduling calls</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-joining-calls-and-the-meeting-page">Joining calls and the Meeting Page</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-retrieving-upcoming-calls">Retrieving Upcoming Calls</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ul>
<h2 id="heading-what-is-stream">What is Stream?</h2>
<p><a target="_blank" href="https://getstream.io/">Stream</a> is an open-source cloud-based platform that provides APIs and SDKs for building scalable and feature-rich real-time applications. It offers pre-built UI components for creating enterprise-grade software apps with features like chat, video, audio, and activity feeds.</p>
<p><a target="_blank" href="https://getstream.io"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726475007023/be45aa40-7794-434a-8f5d-f4b637d97fd8.png" alt="What is Stream" class="image--center mx-auto" width="3114" height="1778" loading="lazy"></a></p>
<p>Here's how I'll use <code>Stream</code> while building the app:</p>
<ul>
<li><p>Set up real-time video and audio calls</p>
</li>
<li><p>Use Stream's UI components to quickly build the interface</p>
</li>
<li><p>Implement key features like <code>video</code> and <code>audio</code> calls</p>
</li>
<li><p><code>Call Types</code> – I'll implement instant meetings and pre-scheduled calls using Stream</p>
</li>
<li><p>Leverage Stream's call and participant objects to manage <code>call state</code></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To fully understand the tutorial, you need to have a basic understanding of <a target="_blank" href="https://www.freecodecamp.org/news/learn-react-key-concepts/">React</a> and <a target="_blank" href="https://theankurtyagi.com/next-js/">Next.js</a>. You’ll also need the following:</p>
<ul>
<li><p><a target="_blank" href="https://getstream.io/chat/docs/sdk/react/">Stream React SDK</a> - provides pre-built UI components for adding video call features quickly.</p>
</li>
<li><p><a target="_blank" href="https://github.com/GetStream/stream-node">Stream Node.js SDK</a> - for managing server-side interactions and keeping Stream's state in sync.</p>
</li>
<li><p><a target="_blank" href="https://clerk.com/">Clerk</a> - a comprehensive user management platform to handle authentication effortlessly.</p>
</li>
<li><p><a target="_blank" href="https://headlessui.com/">Headless UI</a> - provides accessible UI components for building user-friendly applications.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/react-copy-to-clipboard">React Copy-to-Clipboard</a> - allows users to easily copy meeting links within the app.</p>
</li>
<li><p><a target="_blank" href="https://react-icons.github.io/react-icons/">React Icons</a> - offers a library of easily integrated icons.</p>
</li>
</ul>
<h2 id="heading-how-to-build-the-app-interface-with-nextjs">How to Build the App Interface with Next.js</h2>
<p>In this section, I'll guide you through creating the user interface for the video-conferencing app. The interface will allow users to easily create, join, and schedule meetings, as well as view their upcoming meetings.</p>
<p>First, let’s create a Next.js TypeScript project by running the code snippet below:</p>
<pre><code class="lang-bash">npx create-next-app facetime-app
</code></pre>
<p>Then install the following packages:</p>
<ul>
<li><p><a target="_blank" href="https://react-icons.github.io/react-icons/">React icons</a> - a popular React icons package</p>
</li>
<li><p><a target="_blank" href="https://headlessui.com/">Headless UI</a> - provides a set of accessible UI components</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/react-copy-to-clipboard">React-copy-to-clipboard</a> - a lightweight package that enables us to copy meeting links.</p>
</li>
</ul>
<pre><code class="lang-bash">npm install react-icons @headlessui/react react-copy-to-clipboard
</code></pre>
<p>Copy the code snippet below into the <code>app/page.tsx</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { FaLink, FaVideo } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> InstantMeeting <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/InstantMeeting"</span>;
<span class="hljs-keyword">import</span> UpcomingMeeting <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/UpcomingMeeting"</span>;
<span class="hljs-keyword">import</span> CreateLink <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/CreateLink"</span>;
<span class="hljs-keyword">import</span> JoinMeeting <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/JoinMeeting"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [startInstantMeeting, setStartInstantMeeting] =
        useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [joinMeeting, setJoinMeeting] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [showUpcomingMeetings, setShowUpcomingMeetings] =
        useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [showCreateLink, setShowCreateLink] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;button
                className=<span class="hljs-string">' top-5 right-5 text-sm fixed bg-green-500 px-2 w-[150px] hover:bg-green-600 py-3 flex flex-col items-center text-white rounded-md shadow-sm cursor-pointer z-10'</span>
                onClick={<span class="hljs-function">() =&gt;</span> setJoinMeeting(<span class="hljs-literal">true</span>)}
            &gt;
                &lt;FaVideo className=<span class="hljs-string">'mb-[3px] text-white'</span> /&gt;
                Join FaceTime
            &lt;/button&gt;

            &lt;main className=<span class="hljs-string">'w-full h-screen flex flex-col items-center justify-center'</span>&gt;
                &lt;h1 className=<span class="hljs-string">'font-bold text-2xl text-center'</span>&gt;FaceTime&lt;/h1&gt;
                &lt;div className=<span class="hljs-string">'flex flex-col'</span>&gt;
                    &lt;button
                        className=<span class="hljs-string">'text-green-500 underline text-sm text-center cursor-pointer'</span>
                        onClick={<span class="hljs-function">() =&gt;</span> setShowUpcomingMeetings(<span class="hljs-literal">true</span>)}
                    &gt;
                        Upcoming FaceTime
                    &lt;/button&gt;
                &lt;/div&gt;

                &lt;div className=<span class="hljs-string">'flex items-center justify-center space-x-4 mt-6'</span>&gt;
                    &lt;button
                        className=<span class="hljs-string">'bg-gray-500 px-4 w-[200px] py-3 flex flex-col items-center hover:bg-gray-600 text-white rounded-md shadow-sm'</span>
                        onClick={<span class="hljs-function">() =&gt;</span> setShowCreateLink(<span class="hljs-literal">true</span>)}
                    &gt;
                        &lt;FaLink className=<span class="hljs-string">'mb-[3px] text-gray-300'</span> /&gt;
                        Create link
                    &lt;/button&gt;
                    &lt;button
                        className=<span class="hljs-string">'bg-green-500 px-4 w-[200px] hover:bg-green-600 py-3 flex flex-col items-center text-white rounded-md shadow-sm'</span>
                        onClick={<span class="hljs-function">() =&gt;</span> setStartInstantMeeting(<span class="hljs-literal">true</span>)}
                    &gt;
                        &lt;FaVideo className=<span class="hljs-string">'mb-[3px] text-white'</span> /&gt;
                        New FaceTime
                    &lt;/button&gt;
                &lt;/div&gt;
            &lt;/main&gt;

            {startInstantMeeting &amp;&amp; (
                &lt;InstantMeeting
                    enable={startInstantMeeting}
                    setEnable={setStartInstantMeeting}
                /&gt;
            )}
            {showUpcomingMeetings &amp;&amp; (
                &lt;UpcomingMeeting
                    enable={showUpcomingMeetings}
                    setEnable={setShowUpcomingMeetings}
                /&gt;
            )}
            {showCreateLink &amp;&amp; (
                &lt;CreateLink enable={showCreateLink} setEnable={setShowCreateLink} /&gt;
            )}
            {joinMeeting &amp;&amp; (
                &lt;JoinMeeting enable={joinMeeting} setEnable={setJoinMeeting} /&gt;
            )}
        &lt;/&gt;
    );
}
</code></pre>
<p>The code snippet above renders multiple buttons that allow users to perform actions like joining, creating, and scheduling a call. Each button opens a modal that prompts the user to provide additional details specific to the action they are performing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726481911712/286f7349-0d95-419d-97e5-193371307e13.png" alt="facetime-app-home-page" class="image--center mx-auto" width="3110" height="1818" loading="lazy"></p>
<p>Next, let’s create a <code>modals</code> folder within the Next.js app directory and add the following components to the <code>modals</code> folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> app
mkdir modals &amp;&amp; <span class="hljs-built_in">cd</span> modals
touch CreateLink.tsx InstantMeeting.tsx JoinMeeting.tsx UpcomingMeeting.tsx
</code></pre>
<p>The <code>CreateLink</code> modal allows users to provide a description and schedule a time for the call. The <code>InstantMeeting</code> modal lets users start an instant meeting by providing a call description. The <code>JoinMeeting</code> modal enables users to enter a call link and join a meeting. And the <code>UpcomingMeeting</code> modal displays all scheduled upcoming calls.</p>
<h3 id="heading-the-create-link-modal">The Create Link Modal</h3>
<p>Copy the code snippet below into the <code>CreateLink</code> modal:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {
    Dialog,
    DialogTitle,
    DialogPanel,
    Transition,
    Description,
    TransitionChild,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@headlessui/react"</span>;
<span class="hljs-keyword">import</span> { Fragment, SetStateAction, useState, Dispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> CopyToClipboard <span class="hljs-keyword">from</span> <span class="hljs-string">"react-copy-to-clipboard"</span>;
<span class="hljs-keyword">import</span> { FaCopy } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CreateLink</span>(<span class="hljs-params">{ enable, setEnable }: Props</span>) </span>{
    <span class="hljs-keyword">const</span> [showMeetingLink, setShowMeetingLink] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [facetimeLink, setFacetimeLink] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> setEnable(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Transition appear show={enable} <span class="hljs-keyword">as</span>={Fragment}&gt;
                &lt;Dialog <span class="hljs-keyword">as</span>=<span class="hljs-string">'div'</span> className=<span class="hljs-string">'relative z-10'</span> onClose={closeModal}&gt;
                    &lt;TransitionChild
                        <span class="hljs-keyword">as</span>={Fragment}
                        enter=<span class="hljs-string">'ease-out duration-300'</span>
                        enterFrom=<span class="hljs-string">'opacity-0'</span>
                        enterTo=<span class="hljs-string">'opacity-100'</span>
                        leave=<span class="hljs-string">'ease-in duration-200'</span>
                        leaveFrom=<span class="hljs-string">'opacity-100'</span>
                        leaveTo=<span class="hljs-string">'opacity-0'</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">'fixed inset-0 bg-black/75'</span> /&gt;
                    &lt;/TransitionChild&gt;

                    &lt;div className=<span class="hljs-string">'fixed inset-0 overflow-y-auto'</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex min-h-full items-center justify-center p-4 text-center'</span>&gt;
                            &lt;TransitionChild
                                <span class="hljs-keyword">as</span>={Fragment}
                                enter=<span class="hljs-string">'ease-out duration-300'</span>
                                enterFrom=<span class="hljs-string">'opacity-0 scale-95'</span>
                                enterTo=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leave=<span class="hljs-string">'ease-in duration-200'</span>
                                leaveFrom=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leaveTo=<span class="hljs-string">'opacity-0 scale-95'</span>
                            &gt;
                                &lt;DialogPanel className=<span class="hljs-string">'w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all text-center'</span>&gt;
                                    {showMeetingLink ? (
                                        &lt;MeetingLink facetimeLink={facetimeLink} /&gt;
                                    ) : (
                                        &lt;MeetingForm
                                            setShowMeetingLink={setShowMeetingLink}
                                            setFacetimeLink={setFacetimeLink}
                                        /&gt;
                                    )}
                                &lt;/DialogPanel&gt;
                            &lt;/TransitionChild&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Dialog&gt;
            &lt;/Transition&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>The code snippet above renders a form that allows users to input a description and select a time to schedule a call. Once the call is created, the generated link is displayed and can be copied.</p>
<p>Finally, add the <code>MeetingForm</code> and <code>MeetingLink</code> components below the <code>CreateLink</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingForm = <span class="hljs-function">(<span class="hljs-params">{
    setShowMeetingLink,
    setFacetimeLink,
}: {
    setShowMeetingLink: React.Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">boolean</span>&gt;&gt;;
    setFacetimeLink: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">string</span>&gt;&gt;;
}</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [description, setDescription] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [dateTime, setDateTime] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleStartMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        <span class="hljs-built_in">console</span>.log({ description, dateTime });
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Schedule a FaceTime
            &lt;/DialogTitle&gt;

            &lt;Description className=<span class="hljs-string">'text-xs opacity-40 mb-4'</span>&gt;
                Schedule a FaceTime meeting <span class="hljs-keyword">with</span> your cliq
            &lt;/Description&gt;

            &lt;form className=<span class="hljs-string">'w-full'</span> onSubmit={handleStartMeeting}&gt;
                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'description'</span>
                &gt;
                    Meeting Description
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
                    name=<span class="hljs-string">'description'</span>
                    id=<span class="hljs-string">'description'</span>
                    value={description}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDescription(e.target.value)}
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    required
                    placeholder=<span class="hljs-string">'Enter a description for the meeting'</span>
                /&gt;

                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'date'</span>
                &gt;
                    <span class="hljs-built_in">Date</span> and Time
                &lt;/label&gt;

                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'datetime-local'</span>
                    id=<span class="hljs-string">'date'</span>
                    name=<span class="hljs-string">'date'</span>
                    required
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    value={dateTime}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDateTime(e.target.value)}
                /&gt;

                &lt;button className=<span class="hljs-string">'w-full bg-green-600 text-white py-3 rounded mt-4'</span>&gt;
                    Create FaceTime
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p>The <code>MeetingForm</code> component accepts the call description and scheduled time, while the <code>MeetingLink</code> component displays the generated call link and allows users to copy it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingLink = <span class="hljs-function">(<span class="hljs-params">{ facetimeLink }: { facetimeLink: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [copied, setCopied] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> handleCopy = <span class="hljs-function">() =&gt;</span> setCopied(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Copy FaceTime Link
            &lt;/DialogTitle&gt;

            &lt;Description className=<span class="hljs-string">'text-xs opacity-40 mb-4'</span>&gt;
                You can share the facetime link <span class="hljs-keyword">with</span> your participants
            &lt;/Description&gt;

            &lt;div className=<span class="hljs-string">'bg-gray-100 p-4 rounded flex items-center justify-between'</span>&gt;
                &lt;p className=<span class="hljs-string">'text-xs text-gray-500'</span>&gt;
                    {<span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_FACETIME_HOST}</span>/<span class="hljs-subst">${facetimeLink}</span>`</span>}
                &lt;/p&gt;

                &lt;CopyToClipboard
                    onCopy={handleCopy}
                    text={<span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_FACETIME_HOST}</span>/<span class="hljs-subst">${facetimeLink}</span>`</span>}
                &gt;
                    &lt;FaCopy className=<span class="hljs-string">'text-green-600 text-lg cursor-pointer'</span> /&gt;
                &lt;/CopyToClipboard&gt;
            &lt;/div&gt;

            {copied &amp;&amp; (
                &lt;p className=<span class="hljs-string">'text-red-600 text-xs mt-2'</span>&gt;Link copied to clipboard&lt;/p&gt;
            )}
        &lt;/&gt;
    );
};
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726482044698/0cb22caa-3e5a-4f01-9fa2-25c7ce77b08a.png" alt="facetime-app-schedule-popup" class="image--center mx-auto" width="3098" height="1828" loading="lazy"></p>
<h3 id="heading-the-instant-meeting-modal">The Instant Meeting Modal</h3>
<p>Copy the following code snippet into the <code>InstantMeeting</code> modal:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {
    Dialog,
    DialogTitle,
    DialogPanel,
    Transition,
    Description,
    TransitionChild,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@headlessui/react"</span>;
<span class="hljs-keyword">import</span> { FaCopy } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> CopyToClipboard <span class="hljs-keyword">from</span> <span class="hljs-string">"react-copy-to-clipboard"</span>;
<span class="hljs-keyword">import</span> { Fragment, useState, Dispatch, SetStateAction } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">InstantMeeting</span>(<span class="hljs-params">{ enable, setEnable }: Props</span>) </span>{
    <span class="hljs-keyword">const</span> [showMeetingLink, setShowMeetingLink] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [facetimeLink, setFacetimeLink] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> setEnable(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Transition appear show={enable} <span class="hljs-keyword">as</span>={Fragment}&gt;
                &lt;Dialog <span class="hljs-keyword">as</span>=<span class="hljs-string">'div'</span> className=<span class="hljs-string">'relative z-10'</span> onClose={closeModal}&gt;
                    &lt;TransitionChild
                        <span class="hljs-keyword">as</span>={Fragment}
                        enter=<span class="hljs-string">'ease-out duration-300'</span>
                        enterFrom=<span class="hljs-string">'opacity-0'</span>
                        enterTo=<span class="hljs-string">'opacity-100'</span>
                        leave=<span class="hljs-string">'ease-in duration-200'</span>
                        leaveFrom=<span class="hljs-string">'opacity-100'</span>
                        leaveTo=<span class="hljs-string">'opacity-0'</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">'fixed inset-0 bg-black/75'</span> /&gt;
                    &lt;/TransitionChild&gt;

                    &lt;div className=<span class="hljs-string">'fixed inset-0 overflow-y-auto'</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex min-h-full items-center justify-center p-4 text-center'</span>&gt;
                            &lt;TransitionChild
                                <span class="hljs-keyword">as</span>={Fragment}
                                enter=<span class="hljs-string">'ease-out duration-300'</span>
                                enterFrom=<span class="hljs-string">'opacity-0 scale-95'</span>
                                enterTo=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leave=<span class="hljs-string">'ease-in duration-200'</span>
                                leaveFrom=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leaveTo=<span class="hljs-string">'opacity-0 scale-95'</span>
                            &gt;
                                &lt;DialogPanel className=<span class="hljs-string">'w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all text-center'</span>&gt;
                                    {showMeetingLink ? (
                                        &lt;MeetingLink facetimeLink={facetimeLink} /&gt;
                                    ) : (
                                        &lt;MeetingForm
                                            setShowMeetingLink={setShowMeetingLink}
                                            setFacetimeLink={setFacetimeLink}
                                        /&gt;
                                    )}
                                &lt;/DialogPanel&gt;
                            &lt;/TransitionChild&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Dialog&gt;
            &lt;/Transition&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>The code snippet above renders a form that allows users to provide a call description. Once the call is created, the link is generated and available to be copied before starting the call.</p>
<p>Finally, add the <code>MeetingForm</code> and <code>MeetingLink</code> components below the <code>CreateLink</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingForm = <span class="hljs-function">(<span class="hljs-params">{
    setShowMeetingLink,
    setFacetimeLink,
}: {
    setShowMeetingLink: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">boolean</span>&gt;&gt;;
    setFacetimeLink: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">string</span>&gt;&gt;;
}</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [description, setDescription] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleStartMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        <span class="hljs-built_in">console</span>.log({ description });
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Create Instant FaceTime
            &lt;/DialogTitle&gt;

            &lt;Description className=<span class="hljs-string">'text-xs opacity-40 mb-4'</span>&gt;
                You can start a <span class="hljs-keyword">new</span> FaceTime instantly.
            &lt;/Description&gt;

            &lt;form className=<span class="hljs-string">'w-full'</span> onSubmit={handleStartMeeting}&gt;
                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'description'</span>
                &gt;
                    Meeting Description
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
                    name=<span class="hljs-string">'description'</span>
                    id=<span class="hljs-string">'description'</span>
                    value={description}
                    required
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDescription(e.target.value)}
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    placeholder=<span class="hljs-string">'Enter a description for the meeting'</span>
                /&gt;

                &lt;button className=<span class="hljs-string">'w-full bg-green-600 text-white py-3 rounded mt-4'</span>&gt;
                    Proceed
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p>The <code>MeetingForm</code> component accepts the call description, while the <code>MeetingLink</code> component displays the generated call link and allows users to copy it before starting the call.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726482110082/638609aa-e0ae-4cc4-b520-2050966180b4.png" alt="facetime-app-create-instant-facetime" class="image--center mx-auto" width="3098" height="1792" loading="lazy"></p>
<h3 id="heading-the-join-meeting-modal">The Join Meeting Modal</h3>
<p>Copy the code snippet below into the <code>JoinMeeting.tsx</code> file. It renders a form that accepts the call link and redirects users to the call page.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {
    Dialog,
    DialogTitle,
    DialogPanel,
    Transition,
    TransitionChild,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@headlessui/react"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { Fragment, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">JoinMeeting</span>(<span class="hljs-params">{ enable, setEnable }: Props</span>) </span>{
    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> setEnable(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Transition appear show={enable} <span class="hljs-keyword">as</span>={Fragment}&gt;
                &lt;Dialog <span class="hljs-keyword">as</span>=<span class="hljs-string">'div'</span> className=<span class="hljs-string">'relative z-10'</span> onClose={closeModal}&gt;
                    &lt;TransitionChild
                        <span class="hljs-keyword">as</span>={Fragment}
                        enter=<span class="hljs-string">'ease-out duration-300'</span>
                        enterFrom=<span class="hljs-string">'opacity-0'</span>
                        enterTo=<span class="hljs-string">'opacity-100'</span>
                        leave=<span class="hljs-string">'ease-in duration-200'</span>
                        leaveFrom=<span class="hljs-string">'opacity-100'</span>
                        leaveTo=<span class="hljs-string">'opacity-0'</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">'fixed inset-0 bg-black/75'</span> /&gt;
                    &lt;/TransitionChild&gt;

                    &lt;div className=<span class="hljs-string">'fixed inset-0 overflow-y-auto'</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex min-h-full items-center justify-center p-4 text-center'</span>&gt;
                            &lt;TransitionChild
                                <span class="hljs-keyword">as</span>={Fragment}
                                enter=<span class="hljs-string">'ease-out duration-300'</span>
                                enterFrom=<span class="hljs-string">'opacity-0 scale-95'</span>
                                enterTo=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leave=<span class="hljs-string">'ease-in duration-200'</span>
                                leaveFrom=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leaveTo=<span class="hljs-string">'opacity-0 scale-95'</span>
                            &gt;
                                &lt;DialogPanel className=<span class="hljs-string">'w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all text-center'</span>&gt;
                                    &lt;CallLinkForm /&gt;
                                &lt;/DialogPanel&gt;
                            &lt;/TransitionChild&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Dialog&gt;
            &lt;/Transition&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>Add the <code>CallLinkForm</code> below the <code>JoinMeeting</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> CallLinkForm = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [link, setLink] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> router = useRouter();

    <span class="hljs-keyword">const</span> handleJoinMeeting = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent&lt;HTMLFormElement&gt;</span>) =&gt;</span> {
        e.preventDefault();
        router.push(<span class="hljs-string">`<span class="hljs-subst">${link}</span>`</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Join FaceTime
            &lt;/DialogTitle&gt;

            &lt;form className=<span class="hljs-string">'w-full'</span> onSubmit={handleJoinMeeting}&gt;
                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'link'</span>
                &gt;
                    Enter the FaceTime link
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'url'</span>
                    name=<span class="hljs-string">'link'</span>
                    id=<span class="hljs-string">'link'</span>
                    value={link}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setLink(e.target.value)}
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    placeholder=<span class="hljs-string">'Enter the FaceTime link'</span>
                /&gt;

                &lt;button className=<span class="hljs-string">'w-full bg-green-600 text-white py-3 rounded mt-4'</span>&gt;
                    Join now
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726482173301/09881faa-54f8-4293-a186-b608ef5a0e05.png" alt="facetime-app-join-popup" class="image--center mx-auto" width="3104" height="1788" loading="lazy"></p>
<p>Congratulations! You’ve completed the app’s interface.</p>
<h2 id="heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</h2>
<p><a target="_blank" href="https://clerk.com/">Clerk</a> is a user management platform that enables you to add auth to web apps.</p>
<p>You can install the <a target="_blank" href="https://clerk.com/docs/quickstarts/nextjs">Clerk Next.js SDK</a> by running the following code snippet in your terminal:</p>
<pre><code class="lang-bash">npm install @clerk/nextjs
</code></pre>
<p>Create a <code>middleware.ts</code> file within the Next.js <code>src</code> folder and copy the code snippet below into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { clerkMiddleware, createRouteMatcher } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs/server"</span>;

<span class="hljs-keyword">const</span> protectedRoutes = createRouteMatcher([
    <span class="hljs-string">"/facetime(.*)"</span>,
    <span class="hljs-string">"/dashboard"</span>,
    <span class="hljs-string">"/"</span>,
]);

<span class="hljs-comment">//👇🏻 protects the route</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> clerkMiddleware(<span class="hljs-function">(<span class="hljs-params">auth, req</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (protectedRoutes(req)) {
        auth().protect();
    }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
    matcher: [<span class="hljs-string">"/((?!.*\\\\..*|_next).*)"</span>, <span class="hljs-string">"/"</span>, <span class="hljs-string">"/(api|trpc)(.*)"</span>],
};
</code></pre>
<p>The <code>createRouteMatcher</code> function accepts an array containing routes to be protected from unauthenticated users and the <code>clerkMiddleware()</code> function ensures the routes are protected.</p>
<p>Next, import the following Clerk components into the <code>app/layout.tsx</code> file and update the <code>RootLayout</code> function as shown below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {
    ClerkProvider,
    SignInButton,
    SignedIn,
    SignedOut,
    UserButton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: {
    children: React.ReactNode;
}</span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;ClerkProvider&gt;
            &lt;html lang=<span class="hljs-string">'en'</span>&gt;
                &lt;body className={inter.className}&gt;
                    &lt;nav className=<span class="hljs-string">'w-full py-4 md:px-8 px-4 text-center flex items-center justify-between sticky top-0 bg-white '</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex items-center justify-end gap-5'</span>&gt;
                            {<span class="hljs-comment">/*-- if user is signed out --*/</span>}
                            &lt;SignedOut&gt;
                                &lt;SignInButton mode=<span class="hljs-string">'modal'</span> /&gt;
                            &lt;/SignedOut&gt;
                            {<span class="hljs-comment">/*-- if user is signed in --*/</span>}
                            &lt;SignedIn&gt;
                                &lt;UserButton /&gt;
                            &lt;/SignedIn&gt;
                        &lt;/div&gt;
                    &lt;/nav&gt;

                    {children}
                &lt;/body&gt;
            &lt;/html&gt;
        &lt;/ClerkProvider&gt;
    );
}
</code></pre>
<p>After completing this, users will be prompted to create an account or sign in before they can access the application pages.</p>
<p>Finally, create a <a target="_blank" href="https://clerk.com">Clerk account</a> and set up a new Clerk application. Add your Clerk publishable and secret keys to the <code>.env.local</code> file in your project.</p>
<pre><code class="lang-bash">NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=&lt;publishable_key&gt;
CLERK_SECRET_KEY=&lt;secret_key&gt;
</code></pre>
<h2 id="heading-how-to-set-up-stream-in-a-nextjs-app">How to Set Up Stream in a Next.js app</h2>
<p>First, create a <a target="_blank" href="https://getstream.io/">Stream account</a> and set up an organization to house your app. Then, copy the following credentials into your <code>.env.local</code> file:</p>
<pre><code class="lang-bash">STREAM_APP_ID=&lt;your_app_id&gt;
NEXT_PUBLIC_STREAM_API_KEY=&lt;your_stream_api_key&gt;
STREAM_SECRET_KEY=&lt;your_stream_secret_key&gt;
NEXT_PUBLIC_FACETIME_HOST=http://localhost:3000/facetime
</code></pre>
<p>Next, install <a target="_blank" href="https://www.npmjs.com/package/@stream-io/video-react-sdk">Stream React Video SDK</a> and the <a target="_blank" href="https://getstream.io/video/docs/api/#installation">Stream Node.js SDK</a>.</p>
<pre><code class="lang-bash">npm install @stream-io/video-react-sdk @stream-io/node-sdk
</code></pre>
<p>Create a <code>providers</code> folder containing a <code>StreamVideoProvider.tsx</code> file and copy the following code snippet into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { tokenProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/actions/stream.actions"</span>;
<span class="hljs-keyword">import</span> { StreamVideo, StreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useState, ReactNode, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;

<span class="hljs-keyword">const</span> apiKey = process.env.NEXT_PUBLIC_STREAM_API_KEY!;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> StreamVideoProvider = <span class="hljs-function">(<span class="hljs-params">{ children }: { children: ReactNode }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [videoClient, setVideoClient] = useState&lt;StreamVideoClient&gt;();

    <span class="hljs-keyword">const</span> { user, isLoaded } = useUser();

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!isLoaded || !user || !apiKey) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">if</span> (!tokenProvider) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> StreamVideoClient({
            apiKey,
            user: {
                id: user?.id,
                name: user?.primaryEmailAddress?.emailAddress,
                image: user?.imageUrl,
            },
            tokenProvider, <span class="hljs-comment">//👉🏻 pending creation</span>
        });

        setVideoClient(client);
    }, [user, isLoaded]);

    <span class="hljs-keyword">if</span> (!videoClient) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

    <span class="hljs-keyword">return</span> &lt;StreamVideo client={videoClient}&gt;{children}&lt;/StreamVideo&gt;;
};
</code></pre>
<p>Let’s wrap the entire app with the <code>StreamVideoProvider</code> component, which initializes a Stream client to identify each user.</p>
<p>The <code>StreamVideoClient</code> function takes an object containing the API key, the user object with details from Clerk, and a <code>tokenProvider</code>.</p>
<p>Next, let’s create a <a target="_blank" href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations">Next.js server action</a> (<code>tokenProvider</code>) that generates the token.</p>
<p>Create an <code>actions</code> folder, add a <code>stream.actions.ts</code> file, and copy the following code snippet into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//👇🏻 tokenPrvoider function</span>
<span class="hljs-string">"use server"</span>;

<span class="hljs-keyword">import</span> { currentUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs/server"</span>;
<span class="hljs-keyword">import</span> { StreamClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/node-sdk"</span>;

<span class="hljs-keyword">const</span> STREAM_API_KEY = process.env.NEXT_PUBLIC_STREAM_API_KEY!;
<span class="hljs-keyword">const</span> STREAM_API_SECRET = process.env.STREAM_SECRET_KEY!;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> tokenProvider = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> currentUser();

    <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"User is not authenticated"</span>);
    <span class="hljs-keyword">if</span> (!STREAM_API_KEY) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Stream API key secret is missing"</span>);
    <span class="hljs-keyword">if</span> (!STREAM_API_SECRET) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Stream API secret is missing"</span>);

    <span class="hljs-keyword">const</span> streamClient = <span class="hljs-keyword">new</span> StreamClient(STREAM_API_KEY, STREAM_API_SECRET);

    <span class="hljs-keyword">const</span> expirationTime = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>) + <span class="hljs-number">3600</span>;
    <span class="hljs-keyword">const</span> issuedAt = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>) - <span class="hljs-number">60</span>;

    <span class="hljs-comment">//👇🏻 generates a Stream user token</span>
    <span class="hljs-keyword">const</span> token = streamClient.generateUserToken({
        user_id: user.id,
        exp: expirationTime,
        validity_in_seconds: issuedAt,
    });
    <span class="hljs-comment">//👇🏻 returns the user token</span>
    <span class="hljs-keyword">return</span> token;
};
</code></pre>
<p>Finally, update the <code>RootLayout</code> function in the <code>app/layout.tsx</code> file by wrapping the entire application with the <code>StreamVideoProvider</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">"@stream-io/video-react-sdk/dist/css/styles.css"</span>;
<span class="hljs-keyword">import</span> { StreamVideoProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./providers/StreamVideoProvider"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: {
    children: React.ReactNode;
}</span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;ClerkProvider&gt;
            &lt;html lang=<span class="hljs-string">'en'</span>&gt;
                &lt;body className={inter.className}&gt;
                    &lt;StreamVideoProvider&gt;
                        &lt;nav className=<span class="hljs-string">'w-full py-4 md:px-8 px-4 text-center flex items-center justify-between sticky top-0 bg-white '</span>&gt;
                            &lt;div className=<span class="hljs-string">'flex items-center justify-end gap-5'</span>&gt;
                                {<span class="hljs-comment">/*-- if user is signed out --*/</span>}
                                &lt;SignedOut&gt;
                                    &lt;SignInButton mode=<span class="hljs-string">'modal'</span> /&gt;
                                &lt;/SignedOut&gt;
                                {<span class="hljs-comment">/*-- if user is signed in --*/</span>}
                                &lt;SignedIn&gt;
                                    &lt;UserButton /&gt;
                                &lt;/SignedIn&gt;
                            &lt;/div&gt;
                        &lt;/nav&gt;

                        {children}
                    &lt;/StreamVideoProvider&gt;
                &lt;/body&gt;
            &lt;/html&gt;
        &lt;/ClerkProvider&gt;
    );
}
</code></pre>
<p>Congratulations! You've successfully integrated Stream into the Next.js app.</p>
<h2 id="heading-how-to-create-and-join-calls-with-stream">How to Create and Join Calls with Stream</h2>
<p>In this section, you'll learn how to create, schedule, and join calls using the Stream SDK. You'll also learn how to set up the meeting room with the necessary components and fetch upcoming calls from Stream.</p>
<h3 id="heading-creating-and-scheduling-calls">Creating and Scheduling calls</h3>
<p>To create an instant meeting, execute the <code>handleStartMeeting</code> function. It generates a random ID for the call and creates the meeting using the current date and the provided description.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">const</span> client = useStreamVideoClient();
<span class="hljs-keyword">const</span> { user } = useUser();

<span class="hljs-keyword">const</span> handleStartMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!client || !user) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> id = crypto.randomUUID();
        <span class="hljs-keyword">const</span> call = client.call(<span class="hljs-string">"default"</span>, id);
        <span class="hljs-keyword">if</span> (!call) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to create meeting"</span>);

        <span class="hljs-keyword">await</span> call.getOrCreate({
            data: {
                starts_at: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now()).toISOString(),
                custom: {
                    description,
                },
            },
        });

        setFacetimeLink(<span class="hljs-string">`<span class="hljs-subst">${call.id}</span>`</span>);
        setShowMeetingLink(<span class="hljs-literal">true</span>);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
        alert(<span class="hljs-string">"Failed to create Meeting"</span>);
    }
};
</code></pre>
<p>The <code>call.getOrCreate()</code> function accepts an optional call description along with the current date and time to initiate the call.</p>
<p>It also allows you to schedule calls for a specific time in the future. In this case, you can specify the desired date and time, and Stream will automatically schedule the call for that period.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">const</span> client = useStreamVideoClient();
<span class="hljs-keyword">const</span> { user } = useUser();

<span class="hljs-keyword">const</span> handleScheduleMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!client || !user) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> id = crypto.randomUUID();
        <span class="hljs-keyword">const</span> call = client.call(<span class="hljs-string">"default"</span>, id);
        <span class="hljs-keyword">if</span> (!call) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to create meeting"</span>);

        <span class="hljs-keyword">await</span> call.getOrCreate({
            data: {
                <span class="hljs-comment">//👇🏻 only necessary changes</span>
                starts_at: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(dateTime).toISOString(),
                custom: {
                    description,
                },
            },
        });
        setFacetimeLink(<span class="hljs-string">`<span class="hljs-subst">${call.id}</span>`</span>);
        setShowMeetingLink(<span class="hljs-literal">true</span>);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to create Meeting"</span>);
    }
};
</code></pre>
<h3 id="heading-joining-calls-and-the-meeting-page">Joining calls and the Meeting Page</h3>
<p>Recall that the meeting link in the app is declared as:</p>
<pre><code class="lang-jsx"><span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_FACETIME_HOST}</span>/<span class="hljs-subst">${facetimeLink}</span>`</span>
<span class="hljs-comment">// 👉🏻 format: &lt;http://localhost:3000/facetime/&gt;&lt;call.id&gt;</span>
</code></pre>
<p>Therefore, we need to create the <code>/facetime/&lt;callID&gt;</code> route to enable users to join a call. To do this, create a <code>facetime</code> folder with an <code>[id]</code> directory inside, and within that directory, add a <code>page.tsx</code> file. Then, copy the following code snippet into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useGetCallById } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/hooks/useGetCallById"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> {
    StreamCall,
    StreamTheme,
    PaginatedGridLayout,
    CallControls,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useParams, useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FaceTimePage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> { id } = useParams&lt;{ id: <span class="hljs-built_in">string</span> }&gt;();
    <span class="hljs-keyword">const</span> [confirmJoin, setConfirmJoin] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [camMicEnabled, setCamMicEnabled] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> router = useRouter();
    <span class="hljs-comment">//👇🏻 gets call details by ID</span>
    <span class="hljs-keyword">const</span> { call, isCallLoading } = useGetCallById(id);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (camMicEnabled) {
            call?.camera.enable();
            call?.microphone.enable();
        } <span class="hljs-keyword">else</span> {
            call?.camera.disable();
            call?.microphone.disable();
        }
    }, [call, camMicEnabled]);

    <span class="hljs-comment">//👇🏻 enable users to join calls</span>
    <span class="hljs-keyword">const</span> handleJoin = <span class="hljs-function">() =&gt;</span> {
        call?.join();
        setConfirmJoin(<span class="hljs-literal">true</span>);
    };

    <span class="hljs-keyword">if</span> (isCallLoading) <span class="hljs-keyword">return</span> &lt;p&gt;Loading...&lt;/p&gt;;

    <span class="hljs-keyword">if</span> (!call) <span class="hljs-keyword">return</span> &lt;p&gt;Call not found&lt;/p&gt;;

    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">'min-h-screen w-full items-center justify-center'</span>&gt;
            &lt;StreamCall call={call}&gt;
                &lt;StreamTheme&gt;
                    {confirmJoin ? (
                        &lt;MeetingRoom /&gt;
                    ) : (
                        &lt;div className=<span class="hljs-string">'flex flex-col items-center justify-center gap-5'</span>&gt;
                            &lt;h1 className=<span class="hljs-string">'text-3xl font-bold'</span>&gt;Join Call&lt;/h1&gt;
                            &lt;p className=<span class="hljs-string">'text-lg'</span>&gt;
                                Are you sure you want to join <span class="hljs-built_in">this</span> call?
                            &lt;/p&gt;
                            &lt;div className=<span class="hljs-string">'flex gap-5'</span>&gt;
                                &lt;button
                                    onClick={handleJoin}
                                    className=<span class="hljs-string">'px-4 py-3 bg-green-600 text-green-50'</span>
                                &gt;
                                    Join
                                &lt;/button&gt;
                                &lt;button
                                    onClick={<span class="hljs-function">() =&gt;</span> router.push(<span class="hljs-string">"/"</span>)}
                                    className=<span class="hljs-string">'px-4 py-3 bg-red-600 text-red-50'</span>
                                &gt;
                                    Cancel
                                &lt;/button&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    )}
                &lt;/StreamTheme&gt;
            &lt;/StreamCall&gt;
        &lt;/main&gt;
    );
}
</code></pre>
<p>When users visit the meeting page, they are presented with a confirmation message, allowing them to confirm that they want to join the call.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726483083226/26ccb1d9-dc33-4a31-81a9-c4b0a3d00b91.png" alt="facetime-app-live" class="image--center mx-auto" width="3092" height="1834" loading="lazy"></p>
<p>In the code snippet above:</p>
<ul>
<li><p>The <code>useGetCallById</code> hook is a custom function that retrieves call details based on the call ID.</p>
</li>
<li><p>The <code>handleJoin</code> function allows users to join the call and then displays the <code>&lt;MeetingRoom /&gt;</code> component.</p>
</li>
</ul>
<p>Add the <code>MeetingRoom</code> component below the <code>FaceTimePage</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingRoom = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> router = useRouter();

    <span class="hljs-keyword">const</span> handleLeave = <span class="hljs-function">() =&gt;</span> {
        confirm(<span class="hljs-string">"Are you sure you want to leave the call?"</span>) &amp;&amp; router.push(<span class="hljs-string">"/"</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;section className=<span class="hljs-string">'relative min-h-screen w-full overflow-hidden pt-4'</span>&gt;
            &lt;div className=<span class="hljs-string">'relative flex size-full items-center justify-center'</span>&gt;
                &lt;div className=<span class="hljs-string">'flex size-full max-w-[1000px] items-center'</span>&gt;
                    &lt;PaginatedGridLayout /&gt;
                &lt;/div&gt;
                &lt;div className=<span class="hljs-string">'fixed bottom-0 flex w-full items-center justify-center gap-5'</span>&gt;
                    &lt;CallControls onLeave={handleLeave} /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/section&gt;
    );
};
</code></pre>
<p>The <a target="_blank" href="https://getstream.io/video/docs/react/ui-components/core/call-layout/#paginatedgridlayout"><code>PaginatedGridLayout</code></a> arranges participants in a grid layout with pagination, allowing you to manage larger video calls by displaying a set number of participants per page.</p>
<p>The <code>CallControls</code> component provides built-in actions, such as muting, video toggling, and screen sharing, that can be performed during a call. Both components are part of the Stream SDK, making integration seamless.</p>
<p>Additionally, you can switch to the <a target="_blank" href="https://getstream.io/video/docs/react/ui-components/core/call-layout/#speakerlayout"><code>SpeakerLayout</code></a>, which highlights the dominant speaker or shared screen while displaying other participants in a smaller view.</p>
<p>Finally, create a <code>hooks</code> folder containing the <code>useGetCallById.ts</code> file and copy the code snippet below into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Call, useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useGetCallById = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">string</span>[]</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [call, setCall] = useState&lt;Call&gt;();
    <span class="hljs-keyword">const</span> [isCallLoading, setIsCallLoading] = useState(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> client = useStreamVideoClient();

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!client) <span class="hljs-keyword">return</span>;

        <span class="hljs-keyword">const</span> loadCall = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">const</span> { calls } = <span class="hljs-keyword">await</span> client.queryCalls({
                    filter_conditions: { id },
                });

                <span class="hljs-keyword">if</span> (calls.length &gt; <span class="hljs-number">0</span>) setCall(calls[<span class="hljs-number">0</span>]);

                setIsCallLoading(<span class="hljs-literal">false</span>);
            } <span class="hljs-keyword">catch</span> (error) {
                <span class="hljs-built_in">console</span>.error(error);
                setIsCallLoading(<span class="hljs-literal">false</span>);
            }
        };

        loadCall();
    }, [client, id]);

    <span class="hljs-keyword">return</span> { call, isCallLoading };
};
</code></pre>
<p>The code snippet above filters the call list and <a target="_blank" href="https://getstream.io/video/docs/react/guides/querying-calls/#filters">returns the call with a matching ID</a>, allowing users to join the specified call.</p>
<h3 id="heading-retrieving-upcoming-calls">Retrieving Upcoming Calls</h3>
<p>To retrieve upcoming calls from Stream, you can create a custom hook that <a target="_blank" href="https://getstream.io/video/docs/react/guides/querying-calls/#calls-the-user-has-created-or-is-a-member-of">fetches all the calls created by the user</a>, as well as the calls they are a member of.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> { Call, useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useGetCalls = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { user } = useUser();
    <span class="hljs-keyword">const</span> client = useStreamVideoClient();
    <span class="hljs-keyword">const</span> [calls, setCalls] = useState&lt;Call[]&gt;();
    <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> loadCalls = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">if</span> (!client || !user?.id) <span class="hljs-keyword">return</span>;
            setIsLoading(<span class="hljs-literal">true</span>);
            <span class="hljs-keyword">try</span> {
                <span class="hljs-comment">//👇🏻 gets all the calls the user is featured in</span>
                <span class="hljs-keyword">const</span> { calls } = <span class="hljs-keyword">await</span> client.queryCalls({
                    sort: [{ field: <span class="hljs-string">"starts_at"</span>, direction: <span class="hljs-number">-1</span> }],
                    filter_conditions: {
                        starts_at: { $exists: <span class="hljs-literal">true</span> },
                        $or: [
                            { created_by_user_id: user.id },
                            { members: { $in: [user.id] } },
                        ],
                    },
                });

                setCalls(calls);
            } <span class="hljs-keyword">catch</span> (error) {
                <span class="hljs-built_in">console</span>.error(error);
            } <span class="hljs-keyword">finally</span> {
                setIsLoading(<span class="hljs-literal">false</span>);
            }
        };

        loadCalls();
    }, [client, user?.id]);

    <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();

    <span class="hljs-comment">//👇🏻 gets only calls that are yet to start</span>
    <span class="hljs-keyword">const</span> upcomingCalls = calls?.filter(<span class="hljs-function">(<span class="hljs-params">{ state: { startsAt } }: Call</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> startsAt &amp;&amp; <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(startsAt) &gt; now;
    });

    <span class="hljs-keyword">return</span> { upcomingCalls, isLoading };
};
</code></pre>
<p>The <code>useGetCalls</code> hook <a target="_blank" href="https://getstream.io/video/docs/react/guides/querying-calls/#calls-the-user-has-created-or-is-a-member-of">retrieves the list of upcoming calls</a>, which can then be displayed in the <code>UpcomingMeeting</code> modal.</p>
<p>Congratulations! You’ve completed the project for this tutorial.</p>
<p>Check out the live app <a target="_blank" href="https://facetime-on-stream.vercel.app/">here.</a></p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>So far, you’ve learned how to build a video conferencing app. If you'd like to learn more about how you can leverage Stream to build scalable apps, then check out these resources:</p>
<ul>
<li><p><a target="_blank" href="https://getstream.io/chat/">How to integrate Stream Chat Messaging</a></p>
</li>
<li><p><a target="_blank" href="https://getstream.io/video/">How to integrate Stream Audio and Video calls</a></p>
</li>
<li><p><a target="_blank" href="https://getstream.io/activity-feeds/">How integrate Stream Activity Feeds</a></p>
</li>
</ul>
<h2 id="heading-before-we-end"><strong>Before We End...</strong></h2>
<p>I hope you found it insightful and that it has given you enough motivation on how to build apps using awesome developer tools.</p>
<p>These are some of my other most recent blog posts.</p>
<ul>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/state-of-databases-2024">State of Databases for Serverless in 2024</a></p>
</li>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/neon-vs-supabase"><strong>Neon Postgres vs Supabase</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/mongoDB-vs-postgreSQL"><strong>MongoDB vs. PostgreSQL</strong></a></p>
</li>
</ul>
<p>Check out <a target="_blank" href="https://theankurtyagi.com/">my blog</a> for more tutorials like this on awesome developer tools.</p>
<p>Follow me on <a target="_blank" href="https://x.com/theankurtyagi">Twitter</a> to stay updated on my side projects and ongoing learning.</p>
<p>Happy coding.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How a Czech DJ Built a 3D Printing Empire ]]>
                </title>
                <description>
                    <![CDATA[ By Jaime Arredondo In 2012, a young Czech DJ hobbyist was frustrated with the knobs and faders on his music controllers, so went looking for ways to improve them. That’s when he came across 3D printing, and one of the fastest-growing 3D printing comp... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-prusa3d-became-one-of-the-fastest-growing-startups-in-the-world/</link>
                <guid isPermaLink="false">66d45f333a8352b6c5a2aa65</guid>
                
                    <category>
                        <![CDATA[ BUSINESS INTELLIGENCE  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Life lessons ]]>
                    </category>
                
                    <category>
                        <![CDATA[ product development ]]>
                    </category>
                
                    <category>
                        <![CDATA[  Startup Lessons ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 31 Mar 2021 20:57:40 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/03/Josef-Prusa_Prusa-Research_005--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jaime Arredondo</p>
<p>In 2012, a young Czech DJ hobbyist was frustrated with the knobs and faders on his music controllers, so went looking for ways to improve them. That’s when he came across 3D printing, and one of the fastest-growing 3D printing companies in the world was born.  </p>
<p>Today, I’m going to show you exactly how Prusa3D became one of the fastest-growing hardware manufacturers in Europe. Then you can take inspiration from their exact strategy to grow a hardware company and create a community of contributors who will help you develop and promote your project with close to no resources.</p>
<h2 id="heading-some-background-on-prusa">Some background on Prusa</h2>
<p>Josef Prusa, Prusa Research’s founder, is a superstar in the 3D printers industry.</p>
<p>You might have already heard about him but if you haven’t…</p>
<ul>
<li>Prusa Research was founded as a one-man startup in 2012 by Josef Prusa.</li>
<li>His goal with Prusa Research was to create a kind of Thermomix, Europe's favourite all-in-one, easy-to-use kitchen appliance, for 3D printing. He wanted to make a 3D printer that was easy enough that anyone could use it, with guidance on steps and materials.</li>
<li>In 2018 Prusa Research became the fastest-growing tech company in Central Europe (Deloitte Fast 50 2018) after growing <a target="_blank" href="https://www2.deloitte.com/cz/en/pages/press/articles/technology-fast-500-emea-v-top-petce-hned-dve-ceske-firmy.html">17,118%</a> between 2014 and 2018</li>
<li>Prusa Research has grown from humble beginnings to selling 100,000 printers, employing over 410 employees, and setting up a factory in Prague with 9 floors and a hackerspace on the ground floor</li>
<li>The company brought the Maker Faire to Prague for the first time</li>
<li>Prusa’s website has over one million unique visitors per month, its YouTube Channel has more than 144,000 subscribers, and its forum has over 143,000 members</li>
</ul>
<p>Josef Prusa was lucky, but also did a lot of things well that we can all learn from.</p>
<p>How? Let’s dive in.</p>
<h1 id="heading-how-to-solve-a-problem-of-your-own-give-back-and-build-trust-in-growing-communities">How to solve a problem of your own, give back, and build trust in growing communities</h1>
<p>Before the roller coaster started, Josef enrolled in an economics degree to make his parents proud. This resulted in a lot of spare time, so he and his brother began DJing and building their music controllers. </p>
<p><img src="https://lh4.googleusercontent.com/9dMtMOFCRV1bZ_HecTpH2gEA6qBHOlDU4DuxyfWHP7JZ8r6a7sZP3uGPvYJbST1Ag1P4Glpe6z8mZYcV91ocvM8SIHWGUUVOmu7kgEnIQMFNtxxfwztHguIZbBU7pKmO_CTtzQsG" alt="Image" width="733" height="478" loading="lazy"></p>
<p><em>Josef (above), rocking his DJ skills. Little did he know how his life was about to change.</em></p>
<p>He was looking to make his own knobs and faders, but found the search into it too long and challenging. He then found the RepRap project and Mendel 3D printer.  </p>
<p><img src="https://lh4.googleusercontent.com/E1aDCM352WrnBfNzyFNcUm5OAkmqSPXr-qk_99hlG4oX8lkTxuwb6VnrupN2LoZCW5I3Ml2uxtnMXkQGkDy384h4KIAyhWehZxCWu7djASy2Jnm3z5dtaISi93SAT5KYkKtUOny_" alt="Image" width="723" height="398" loading="lazy"></p>
<p>As you may know, RepRap is a community project started by Doctor Adrian Bowyer at the University of Bath and it kickstarted the desktop 3D printing craze.</p>
<p>The basic idea is that a 3D printer can print as many parts as possible for another 3D printer and as a result, decreases its cost.</p>
<p>But when Josef was building his Mendel Printer, he was finding it too complex. It required many different screw sizes, there were no slots for nuts, and very few parts were push-to-fit.</p>
<p>So he improved the Mendel by making a simpler version; the Simplified Mendel [sic], and shared the designs on GitHub with the rest of the RepRap community. </p>
<p>The community caught up with his simplified model and started using it over the original, and that’s when people started noticing him. </p>
<p>Takeaways:</p>
<ul>
<li>If you’re a student, have spare time, and/or have no dependents, enjoy this time to experiment and try new things. What are you curious about building?</li>
<li>Identify and solve a problem of your own.  What tools are you using? What’s currently frustrating you about them?</li>
<li>Fix or simplify what’s not working. If you don’t know how, what skills would help you? Learn those.</li>
<li>Share your solution in a community that’s active and that has the same problem. You’ll benefit from exposure and feedback to make your solutions better. This way you’ll start building trust among a like-minded audience and you’ll be in touch with what people want.</li>
</ul>
<h1 id="heading-how-to-create-your-first-prototype">How to create your first prototype</h1>
<p>When Josef was trying to solve his problem, printers were still missing one key component to have the ABS plastic print successful: a heated bed. Without it, prints warped and deformed away from the bed.</p>
<p><img src="https://lh5.googleusercontent.com/fB8aJ9C9S_tM_3hzz7WI5olf_HNXAbjO7Lrm3e8MCsfxIvG1TXl9i3SqjYyhNjdRVPkCuYpAKD9M4PAvXwDIiYGPobfLz2ZjdhFGYLHNjX_B26XeA2c8hh-vQb_PD_uqC8nO7Qyc" alt="Image" width="574" height="484" loading="lazy"></p>
<p>To tackle this problem, he came up with a rudimentary prototype (shown above) which consisted of a resistance wire stuck between two sheets of acrylic. It didn’t last very long.</p>
<p><img src="https://lh5.googleusercontent.com/Rmv56o7815WM3I7Bjwb30-UVrnDle0OV5PS5RH4s2QcTwH_vv8T04sM6L8vWqWxI7o4xr6RsW-Ci7QPloP305EnqoBUpGQOxL6H7usSol4ZRkHEARj-s65dv0XNx_9zN1AUnoTlM" alt="Image" width="572" height="487" loading="lazy"></p>
<p>Without letting the setbacks defeat him, he went on to create a second version. This one used a tile instead of acrylics, which was an improvement. But still, it only reached about 90 degrees Celsius, which wasn’t enough either.</p>
<p><img src="https://lh6.googleusercontent.com/5jgo8YFW8Ac8Ca3S-R0QVnsVRvFBsSwnZPVGPpj8ZLNy5K55DLs5qSn48txNilV9iYX2sCpAqo5YkODqUU8B8VJ4OI8EOqVMobnguJHbsbZvSJw-hsZ5AO7XV5QQK-MBgDo7U9gb" alt="Image" width="667" height="522" loading="lazy"></p>
<p>After nearly six months of persistent work, the PCB Heatbed MK1 (above) was complete. It was the first real product he created. </p>
<p>This new heatbed could reach 110ºC, more than enough for ABS and other high-temperature plastics.</p>
<p>But many parts of the heatbed were either too expensive or difficult to get, so he redid many on his own. </p>
<p>He soon started receiving requests to print his Prusa Mendel’s parts. He also organized a few local build events, where everybody could build their own parts. </p>
<p>There was so much demand that it was time for Josef to officially start Prusa 3D with his brother Michal.</p>
<p>What’s interesting is that he didn’t start with the company name, the logo, and so on – rather, he started with a problem he had himself, and then he shared publicly, both his problem and his solutions, with others. </p>
<p>By sharing what he was doing with others, people who had the same problem could order his solution. And there were many people who shared his problem, which translated into many orders.</p>
<p>It’s also important to appreciate the persistence and patience it takes to continue iterating for six months in order to create a product that works and one that people want to use. </p>
<p>Josef and his brother began selling their first parts without an e-shop and instead sold them through email and a phone number on a webpage. They also hadn’t perfectly optimized their packaging yet. In the beginning, they packed their heatbeds in a pizza box and shipped them off to their clients.</p>
<p>Josef and Michal didn’t let the lack of a perfect tech solution get in their way. They simply found a way that was good enough to get their idea out the door and then made it better as they went along.</p>
<p>They were also proactive in creating awareness and trust with their audience. In the early days, they kickstarted the community by organizing presentations and going to events to educate people about the possibilities of this new 3D printing idea.</p>
<p>Josef also embodied honesty in his sales. If people came to him but were looking for something he didn’t sell or was a poor fit, he just told them which technology they should use instead.  </p>
<p>This earned him a community of loyal users who trusted him and regularly came back to share prints and hacks on the new Prusa Printer's online hub. Whenever Prusa was criticized in their Youtube comment section, a flock of fans spoke up in their defense. </p>
<p>Takeaways:</p>
<ul>
<li>Start simple before having everything figured out.</li>
<li>Let go of perfectionism or trying to look like established companies. What is good enough for the stage you’re at to satisfy the needs of the people you can serve? If it’s packaging your products in a pizza box instead of providing a delightful unboxing experience, so be it. If it’s not having an e-shop but just an email and a phone number on a simple website, so be it.</li>
<li>Focus on creating a solution that solves the problem for good. It might take some time, but if it hasn’t been solved yet, there’s a good chance it’s because it’s hard to solve. Being persistent and patient requires you to commit and invest at the beginning, but once you get through the other side of the problem, you’ll have something people will flock to. It took Josef Prusa six months before he found a proper solution for his heatbed.</li>
<li>Be radically honest and defend the best interests of your customers. If there is someone else who can serve them better, redirect them there. This builds trust, as people will remember how you treated them with respect.</li>
</ul>
<h1 id="heading-should-you-do-everything-yourself-or-delegate-certain-tasks">Should you do everything yourself, or delegate certain tasks?</h1>
<p>When we start something and it gets some traction, or even if we just anticipate the traction it can get, thinking about everything that we have to deal with can become overwhelming.</p>
<p>There might be barcode visualization, trademark registration, label design, building walls, building websites, accounting, invoicing, digging drains, dealing with bankers, installing equipment, video-editing, dealing with customer support, and more. </p>
<p>Most of the time we’re not even remotely competent at more than one or two of those things.</p>
<p>So this is the time when we hit a fork in the road. Do we hire or outsource to delegate, or do we do it ourselves?</p>
<p>Prusa’s beginnings are an interesting example of how to go through this period and build for the long term. Below, Josef <a target="_blank" href="https://www.tctmagazine.com/additive-manufacturing-3d-printing-news/pushing-prusa/">explains</a> how they went about preparing to scale:</p>
<blockquote>
<p><em>“We never had resellers so we were always in direct contact with the customers in the community and this proved very important for us because you have instant feedback from the people.</em>  </p>
<p><em>If you are just a manufacturer and somebody else is doing the selling for you, you don’t always get all the information back.</em>  </p>
<p><em>In the beginning, it was much tougher for us to do it this way because we not only needed to learn how to make the printers at scale but we also needed to learn how to run a big webshop and how to do the customer support for all these people. It was more difficult but now it’s paying off that we have this direct contact and know how to run every part of the business on our own.”</em></p>
</blockquote>
<p>It wasn’t until October 2013, three years after finishing the initial prototype, that they hired their first employee, Hanka.</p>
<p>How do you get the cash flow to hire people? Well, you sell in advance and produce after. In the beginning, Prusa always had a two week lead time for customers to get a printer. </p>
<p>As they continued to grow, they also hired a Foxconn engineer to deal with quality and a couple more software engineers to lead the engineering team.</p>
<p>They could have spent months or years trying to raise funding through VC or Kickstarter in order to hire people, outsource production and grow much faster. </p>
<p>But they decided instead to invest in the slower and more demanding path of figuring it out on their own, and keeping contact with the customers and their needs. This path has proven to be a much better strategy in the long run.  </p>
<p>In 2014, Prusa Research had a revenue of 149.000€, which then grew to 70 million €, employing over 250 employees in 2019 just by bootstrapping the business.</p>
<p>If you aim to change the system, you need to be able to exist independently of it. </p>
<p>Takeaways:</p>
<ul>
<li>Embrace DIY and learn to do the critical parts of the business yourself. What skills do you need to learn? Where can you learn them?</li>
<li>Once you can no longer do it yourself, understand what needs to be done, and when you have enough cash flow, hire people to do the work with you.</li>
</ul>
<h1 id="heading-what-prusa-stands-for">What Prusa stands for</h1>
<p>Prusa has exploded because it does a few things very well by always putting their customers' needs first without compromising their values or their price. This in turn helps them build a strong virtuous cycle for their development.</p>
<h3 id="heading-prusa-has-a-long-term-vision">Prusa has a long-term vision</h3>
<p>Josef knows what he wants Prusa to become. He wants his printers to be able to print any object with any material and through guided steps, much like a Thermomix for 3D printing. And he wants the least tech-savvy person to be able to operate it. </p>
<p>Having this clarity helps him and everyone in the company align their efforts towards a common goal.</p>
<h3 id="heading-they-have-amazing-customer-support">They have amazing customer support</h3>
<p>The company also keeps investing in the way it cares for its customers. </p>
<p>They go to great lengths to test every single part of their printers to ensure quality but even that isn’t enough to cover everything – so that’s why they have support. </p>
<p>Almost 20% of their employees work in customer support. They have 12,000 live chats per month in nine languages and deal with over 11,000 emails each month.</p>
<h3 id="heading-prusa-provides-high-quality-products">Prusa provides high quality products</h3>
<p>Investing in making the designs of their 3D printers more functional, simple, and of high quality allows them to avoid competing with the nicer-looking but more expensive 3D printers.</p>
<h3 id="heading-they-make-an-affordable-printer">They make an affordable printer</h3>
<p>This means that they are not too expensive for everyday consumers, and not too cheap for companies. </p>
<p>They’ve also made their 3D printer upgradable because it saves money for their customers and it builds their clients’ autonomy by helping them learn about the construction of the printer’s hardware. </p>
<h3 id="heading-all-prusas-work-is-open-source">All Prusa's work is open source</h3>
<p>Prusa’s clients are “normal Joes” as Josef describes them, and most don’t care much about open source. But the company does.</p>
<p>Those who care about open source provide valuable contributions that can be added back into the products. Some people will make improvements, some will fill in new code, and all of it helps make the printers better. </p>
<p>The open-source approach is also good for users. Those who want to do modifications find it much simpler because they have the original sources for the printer parts, the firmware, and the electronics.</p>
<p>Josef even has a tattoo of the OHSWA logo to keep himself accountable and honest to the open source vision.</p>
<p><img src="https://lh6.googleusercontent.com/_HpRsRZxaIgltuy6qV4kPbS--swoExxD1D_6rWsblfLzIzWfRSsEeJqTu2mgNwbMRDSObQDGC5N89_e5MFtc2bt7KWcoo-xFy4vwQVSK2VQ7c_LEYo3Wt4aeFiGV8kA6Z8_3nDIR" alt="Image" width="640" height="469" loading="lazy">
<em>Source: <a target="_blank" href="https://3dprintingindustry.com/news/aleph-objects-prusa-research-3d-printing-community-others-react-ultimaker-patent-application-107215/">3D printing Industry</a></em></p>
<p>Open source makes it easy for the idea to spread and upskill people who can find new use cases to increase the company’s pace of innovation, and it makes it more affordable to its clients.</p>
<h3 id="heading-they-partner-with-distributors-and-support-their-clients-even-though-it-decreases-their-margins">They partner with distributors and support their clients even though it decreases their margins</h3>
<p>Another counterintuitive thing Prusa does for its clients is that it supports the customers serviced by other distributors. </p>
<p>Many companies would forfeit this channel because it demands high margins and the distributors don’t do support. But they do these distribution partnerships anyway to make it easier for the people they serve to discover and access their printers. </p>
<p>And even if they make no extra money through these channels, they still give them the same level of support as those who buy from their website. </p>
<p>Why? Because caring for their customers is what makes it safe for them to then recommend Prusa to their friends and family, driving more business by word of mouth. </p>
<p>Takeaways:</p>
<ul>
<li>What is the long term vision of what you’re doing? What happens as you keep developing your organization? What results are you helping people achieve?</li>
<li>How can you invest in better supporting your customers?</li>
<li>What parts of your project can you give away for people to build and learn with you?</li>
<li>What partnerships can you build to distribute your project in places where people need it?</li>
</ul>
<h1 id="heading-how-prusa-builds-and-invests-in-the-community">How Prusa Builds and Invests in the Community</h1>
<p>We’ve already spoken a lot about how much they invest in customer support, but Prusa also invests heavily in their community. </p>
<p>This does two things: it builds proof of what their product does in the world, and it helps scale even further what their customer support service can do. </p>
<p>To gather their community they do a few things.</p>
<p>The first thing Prusa does is offer two options to customers: They can either buy printers as kits or assembled. 80% of clients buy the printers in kits. Besides saving time in production, this allows the clients to learn how to build their printers and understand how they work. </p>
<p>This approach is raising a generation of makers who can create and fix instead of throw away, building a lot of goodwill for the Prusa brand. </p>
<p>To keep in touch with the community, in the early days Josef Prusa tried to go to as many shows as possible so that he could talk to fans face to face and hear about the awesome projects that could come to life with the help of their printers. He went to Maker Faires and to DIY or 3D-printing events.</p>
<p>Now that the company has grown so much, he can’t go to as many events. But before the pandemic started, there was a team of three to ten people traveling around the world two to four times a month. And they were also organizing their Maker Faire in the Czech Republic. </p>
<p>Takeaways:</p>
<ul>
<li>Where are your community members hanging out? What blogs or magazines do they read? What podcasts do they listen to? What events do they go to? What youtube channels do they watch? What newsletters do they subscribe to? Who do they follow on Twitter, Facebook, or Linkedin? What forums or groups do they participate in?</li>
<li>What resources do they need to get started that you can facilitate? How can you give them the tools to create what you do?</li>
<li>How can you invite users to participate in the development of your product? Can you open your files and designs for them? How can you invite them to give back and showcase their work or the skills they’re building thanks to you? Where could you use this as proof that your product works?</li>
</ul>
<h1 id="heading-how-to-engage-the-community">How to Engage the Community</h1>
<p>Many people understand the value of giving free stuff away online to attract a crowd. But I feel that many entrepreneurs haven’t embraced the opportunity and the value of connecting the people in their community with each other. </p>
<p>This is a very powerful idea that can go a long way in building trust and reciprocity with your brand and in getting the community members to spread the word and interact with stories of what you do.</p>
<p>One more powerful thing that Prusa is doing is figuring out how to connect the isolated Maker tribe, and at scale. </p>
<p>Once they gather their community by giving away their designs and connecting with them at events, the next challenge is getting these people to engage. And Prusa does a remarkable job at that. </p>
<p>They’ve created a series of resources that make it easy for people to learn the skills and tools they need to become active members of the community.</p>
<p>In their online hub, they share resources for learning and practice, such as a library of 3D printing models with files, and free guides on how to start 3D printing. </p>
<p>Once people are on their website to grab these resources, they can connect with each other locally or online through a map or in the forums to reach out for support or to go for a beer. </p>
<p>As a quick overview, Prusa provides the resources needed to learn the tools and the skills required to set up and hack a 3D printer. There are manuals, such as a free ebook to teach the basics of 3D printing, assembly instructions in video and ebook form, troubleshooting guides, and of course the downloadable drivers and firmware.</p>
<p>Once people have what they need to learn the basics, they can jump into the Forum to talk about their printer model, stay up to date with General Announcements and releases, find those community members in the Hall of Fame, and discuss the software.</p>
<p>Takeaways:</p>
<ul>
<li>What resources does your audience need to develop the skills required to use your product and to participate in the community in order to help each other?</li>
<li>Once they trust you, how can you connect them to find support among their peers? What exchanges can you facilitate or what spaces can you create for them to gather and talk about their questions?</li>
</ul>
<h1 id="heading-in-summary">In Summary</h1>
<p>Prusa’s approach helped them grow over 17,000% without a sales team, only through word of mouth.</p>
<p>It helps that they serve the fast-growing 3D printing market, but still.</p>
<p>Prusa has become a big player and a beloved brand in their industry, proving that  you don’t need a huge marketing team or budget to get similar results. You just need a smart and intentional plan.</p>
<p>Here are the key takeaways you can borrow, modify, and adapt for your own business based on Prusa’s real-life marketing tactics:</p>
<h3 id="heading-takeaway-1-build-a-skill-to-solve-a-problem-of-your-own">Takeaway #1: Build a skill to solve a problem of your own</h3>
<p>What tool are you using that is not working as you wish it did? Learn the skills to fix or simplify what’s not working. </p>
<h3 id="heading-takeaway-2-share-your-solution-in-public">Takeaway #2: Share your solution in public</h3>
<p>Once you create your first working solution, share it with communities who already use these tools and have the same problem as you do. </p>
<p>This builds trust, reciprocity, and if people want to buy your solution or they have other problems you can build on, they can tell you.</p>
<h3 id="heading-takeaway-3-if-its-your-first-time-be-patient">Takeaway #3: If it’s your first time, be patient</h3>
<p>When we first start, we don’t have all the skills we need to find a solution to a problem. Be patient and persistent and embrace failure and rejection. It’s by getting into action that you’ll figure out what’s not working or missing, and what needs adjusting.</p>
<h3 id="heading-takeaway-4-start-simple-even-if-you-dont-have-everything-figured-out">Takeaway #4: Start simple, even if you don’t have everything figured out</h3>
<p>Let go of perfectionism or trying to look like an established company. What is good enough at the stage you’re at to satisfy the needs of the people you can serve? What is good enough for now to solve other people's problems, build your product, and ship it?</p>
<h3 id="heading-takeaway-5-learn-to-do-everything-yourself-and-become-autonomous">Takeaway #5: Learn to do everything yourself and become autonomous</h3>
<p>Don’t delegate too soon. If your goal is to change the system, you’ll have to learn to be autonomous early on and stay in close contact with your customers. </p>
<p>When the time comes to delegate, you’ll know what needs to be done and hire the right people for it. </p>
<h3 id="heading-takeaway-6-be-radically-honest-and-defend-the-interests-of-your-customers">Takeaway #6: Be radically honest and defend the interests of your customers</h3>
<p>If there is a competitor who can serve them better, redirect them there. It will build trust as people will remember how you treated them with respect. </p>
<h3 id="heading-takeaway-7-be-clear-on-what-you-stand-for">Takeaway #7: Be clear on what you stand for</h3>
<p>What is the long term vision of your project? If money and growth is a means to an end, what is that end meant to achieve? What can you do to accelerate or scale this? </p>
<p>For Prusa, it was investing in outstanding customer support and sharing their work in open source to create both a delightful experience and to involve outside experts in their innovation.</p>
<h3 id="heading-takeaway-8-find-and-gather-your-community">Takeaway #8: Find and gather your community</h3>
<p>Go meet your community where they hang out to stay in touch with their needs and to connect with them. What forums or groups do they participate in? What events do they go to?</p>
<p>Once you’ve found them, create spaces for them to gather and connect. Josef Prusa started by participating in the RepRap forums and by going to Maker events. Later on, they started organizing their Maker Faire in the Czech Republic.</p>
<h3 id="heading-takeaway-9-engage-the-community">Takeaway #9: Engage the community</h3>
<p>Give them the resources and tools they need to get started. Then invite them to participate in the development of your product by opening your designs. </p>
<p>For those who contribute, you can showcase their work and skills to show your gratitude, and use these contributions also as proof that your product and community work.</p>
<p>Thanks for reading. Inspiration for this article came from The Road to 100,000 Original Prusa 3D printers. You can watch it here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/xX3pDDi9PeU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why I Struggled to Price My Startup, and How I Finally Launched Tueri.io ]]>
                </title>
                <description>
                    <![CDATA[ By Dane Stevens Pricing can be the life or death of a bootstrapped startup. When I started the process of trying to price my startup I became overwhelmed with questions and doubts. Should we have usage-based or a tiered pricing model? How much storag... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-struggle-to-price-my-startup-and-how-i-finally-launched-tueri-io/</link>
                <guid isPermaLink="false">66d84e8b175544516f70c460</guid>
                
                    <category>
                        <![CDATA[ Business development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ pricing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ startup ]]>
                    </category>
                
                    <category>
                        <![CDATA[  Startup Lessons ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 14 Aug 2019 16:36:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/08/photo-1543286386-2e659306cd6c.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Dane Stevens</p>
<h2 id="heading-pricing-can-be-the-life-or-death-of-a-bootstrapped-startup">Pricing can be the life or death of a bootstrapped startup.</h2>
<p>When I started the process of trying to price my startup I became overwhelmed with questions and doubts. Should we have usage-based or a tiered pricing model? How much storage can we offer at each tier? Should we offer a free plan? What should the base price be? What if no one wants to pay for this? What if Tueri is a failure?</p>
<p>I have spent countless hours trying to answer these questions and pricing has been the single most daunting part of launching <a target="_blank" href="https://tueri.io">Tueri</a>. Tueri is a completely bootstrapped startup and ultimately, pricing can be the life or death of it. Investor money is non-existent, meaning the company needs to be profitable at every stage of growth.</p>
<p>It boils down to these two essential questions:</p>
<ol>
<li>Can I provide exceptional value and service to my customers at this price?</li>
<li>At this price, can Tueri continue to grow and be on the leading-edge so I can continue to deliver exceptional value and service?</li>
</ol>
<p>These two questions may seem at odds, but they are essential for building a long-term, customer-centric company.</p>
<h2 id="heading-what-is-tueri">What is Tueri?</h2>
<p>The word <strong>Tueri</strong> is a Latin word that means: preserve. Tueri is an image management and optimization platform based on the idea of an <strong>immutable</strong> master image.</p>
<p>Tueri uses just-in-time image transformation, compression and conversion to deliver the perfect image to each one of your users in just milliseconds.</p>
<p>The idea for Tueri came during my work as an application developer. As a developer, I build customer-incentive applications for automotive parts companies. The basic premise for these sites is the more auto parts a customer (mechanic shop) buys, the more points they earn. Customers redeem points online for everything from golf clubs to TVs to vacations.</p>
<p>These websites receive daily file feeds from vendors with new, discontinued, and updated products. There are <em>thousands</em> of products and no standardization on image dimensions, file sizes or image hosting HTTP protocol. I built Tueri to solve these problems.</p>
<p>The first version of Tueri was a simple proxy server designed to fetch remote HTTP images and re-serve them over HTTPS, removing insecure-content warnings on our customer-incentive sites.</p>
<p>The next version was a simple one-page PHP script using GraphicsMagick. This script fetched a remote image, resized the image if it was over a predefined width, stored it on the server and served the image over HTTPS.</p>
<p>These iterations soon progressed in scope and features and somewhere along the way, I realized it was saving me obscene amounts of time.</p>
<p><strong>I needed to share this with other developers.</strong></p>
<p>After a lot of work converting a personal project into something I could host for others, I was ready to launch. The only problem was, I had no idea how to price it.</p>
<h2 id="heading-should-we-offer-a-free-plan">Should we offer a free plan?</h2>
<p>Here are some reasons why a free plan makes sense:</p>
<ul>
<li>User Acquisition — Offering a free plan can help you acquire a ton of new users. This, in turn, should drive growth through word of mouth marketing.</li>
<li>Upselling — A user on the free plan can be upsold to a paid plan.</li>
<li>Supporting the Community — In my jobs as a developer, I have relied on countless free services and have benefited greatly from the open-source community.</li>
</ul>
<p>My fear of not offering a free plan was that I would have a very hard time acquiring new users. I struggled with this question what seemed like hundreds of times, so I did some research.</p>
<p>I researched countless other startups. I wanted to know whether they offered a free plan, what features they included, what percentage of users were on it and if they were profitable.</p>
<p>I discovered the following:</p>
<ul>
<li>Free users will drive growth through word of mouth, but they will drive more free plan growth.</li>
<li>A free plan will help you acquire more users, but only a very small percentage of those users will ever convert to a paid plan.</li>
<li>A huge percentage of support is dedicated to free users.</li>
<li>A free plan product is often inferior due to costs associated with back-end services.</li>
<li>This inferior product is the product that people come to know your business by.</li>
</ul>
<h3 id="heading-the-free-plan-decision">The Free Plan Decision</h3>
<p>I have decided not to offer a free plan in order to dedicate 100% of our resources to our paying customers. This ensures both the quality of the product and exceptional customer service.</p>
<h2 id="heading-should-we-have-a-usage-based-or-tiered-pricing-model">Should we have a usage-based or tiered pricing model?</h2>
<h4 id="heading-usage-based-pricing">Usage-Based Pricing</h4>
<p>Pros:</p>
<ul>
<li>Only pay for what you use.</li>
<li>Better for companies with seasonal usage fluctuations.</li>
<li>Better for individual developers where low monthly spend is a priority.</li>
</ul>
<p>Cons:</p>
<ul>
<li>Difficult to understand what your actual monthly bill will be.</li>
<li>Potentially drastic monthly bill fluctuations.</li>
<li>Harder for a developer to pitch to their company.</li>
<li>Customer mentality is focused on limiting usage to keep costs down, thus decreasing the perceived value of the product.</li>
</ul>
<h4 id="heading-tiered-pricing">Tiered Pricing</h4>
<p>Pros:</p>
<ul>
<li>Easy to understand your monthly bill.</li>
<li>No monthly bill fluctuations.</li>
<li>A fixed monthly amount is an easy pitch for a developer to make to their company.</li>
<li>Customer mentality is focused on getting the most value out of their plan, thus increasing the perceived value of the product.</li>
</ul>
<p>Cons:</p>
<ul>
<li>Harder to appeal to all use cases.</li>
<li>Not as ideal for seasonal use customers.</li>
<li>Hard to appeal to individual developers with a minimal budget.</li>
</ul>
<p>I scoured hundreds of pricing pages from different Software as a Service (Saas) companies; some simple, some complex. I continually gravitated toward tiered pricing models based on the fact that they were easier to understand. Many companies with usage-based pricing dedicate an entire page to explaining how to calculate your monthly bill. Even after following the examples I still could not say for certain what they would cost me.</p>
<p>Let's talk about an example of usage-based pricing for Tueri. Let's say its priced per image transformation. You have a simple responsive website with 10 pages and 10 images per page for a total of 100 images. You check Google Analytics for device usage: you have one desktop resolution, one laptop resolution, two tablet resolutions (portrait and landscape) and two mobile resolutions (portrait &amp; landscape). If every image on every page gets viewed ten times by each resolution you have a total of 6,000 transformations.</p>
<p>Easy right? Not exactly.</p>
<p>In reality, you may have 10,000, 50,000, 100,000 or more page views a month. You could have 20+ different resolutions, HiDPI displays, pages with varying levels of views, and new images added regularly.</p>
<p>You can see how this gets complicated.</p>
<h3 id="heading-the-pricing-model-decision">The Pricing Model Decision</h3>
<p>While it was not an easy decision to make, I concluded that tiered pricing was right for Tueri due to its transparency and simplicity.</p>
<h2 id="heading-what-should-the-base-price-be">What should the base price be?</h2>
<p>This has to be the single most difficult question to answer, at least it was for me. One that I will continue to ask and re-evaluate throughout the lifetime of Tueri.</p>
<p>I had to assess:</p>
<ul>
<li>Are we a premium or a value company?</li>
<li>What type of users do we want to focus our service on?</li>
<li>Do we want to serve millions of customers at a low price or thousands of customers at a higher price?</li>
<li>Can we continue to provide exceptional service to our customers at this price?</li>
<li>What is the time value we are providing to our customers?</li>
<li>What is the monetary value we are providing to our customers?</li>
<li>What does our competition charge, are they profitable, and is their business sustainable?</li>
</ul>
<p>It boils down to defining priorities:</p>
<ul>
<li>I want to provide an exceptional level of service to fewer customers.</li>
<li>I am in this for the long-haul and our pricing needs to be sustainable.</li>
</ul>
<h3 id="heading-the-base-price-decision">The Base Price Decision</h3>
<p>Pricing should not be static, you should assess it over time based on feedback from customers, the added value from new features, and operating costs.</p>
<p>We are constantly evaluating our pricing and <a target="_blank" href="mailto:dane.stevens@tueri.io">I would love to know</a> your use case and if our pricing works for you.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I finally realized that I was never going to have it all figured out, so I made the best decisions I could with the available information and launched.</p>
<p>There is no definitive answer to pricing your startup, but keep these things in mind and you won't go wrong:</p>
<ul>
<li>Exceed your customer's expectations in the service and value you provide.</li>
<li>Price your business for growth.</li>
<li>Launch your startup.</li>
<li>Reevaluate your pricing continually.</li>
</ul>
<hr>
<p>Do you have any tips or questions about pricing your startup? Get in touch at <a target="_blank" href="mailto:dane.stevens@tueri.io">dane.stevens@tueri.io</a>.</p>
<hr>
<p>_Originally published at <a target="_blank" href="https://tueri.io/blog/2019-08-14-the-struggle-to-price-my-startup-and-how-i-finally-launched-tueri/?utm_source=Freecodecamp&amp;utm_medium=Post&amp;utm_campaign=Pricing">Tueri.io</a>_</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The boring technology behind a one-person Internet company ]]>
                </title>
                <description>
                    <![CDATA[ By Wenbin Fang Listen Notes is a podcast search engine and database. The technology behind Listen Notes is actually very very boring. No AI, no deep learning, no blockchain. “Any man who must say I am using AI is not using True AI” :) After reading t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-boring-technology-behind-a-one-person-internet-company/</link>
                <guid isPermaLink="false">66d4617c2472e5ed2fa07bc7</guid>
                
                    <category>
                        <![CDATA[ actor model ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Entrepreneurship ]]>
                    </category>
                
                    <category>
                        <![CDATA[ podcast ]]>
                    </category>
                
                    <category>
                        <![CDATA[ solopreneur  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 10 Jun 2019 21:51:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/06/1_83ZzjS6ZhVWvZdnoozElOw.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Wenbin Fang</p>
<p><a target="_blank" href="https://www.listennotes.com/">Listen Notes</a> is a podcast search engine and database. The technology behind Listen Notes is actually very very boring. No AI, no deep learning, no blockchain. <a target="_blank" href="https://www.youtube.com/watch?v=4sJY7BTIuPY">“Any man who must say I am using AI is not using True AI”</a> :)</p>
<p>After reading this post, you should be able to replicate what I build for Listen Notes or easily do something similar. You don’t need to hire a lot of engineers. Remember, <a target="_blank" href="https://www.crunchbase.com/organization/instagram#section-funding-rounds">when Instagram raised $57.5M and got acquired by Facebook for $1B</a>, they had only <a target="_blank" href="https://www.businessinsider.com/instagram-employees-and-investors-2012-4">13 employees</a> — not all of them were engineers. The Instagram story happened in early 2012. It’s 2019 now, it’s more possible than ever to build something meaningful with a tiny engineering team — even one person.</p>
<p>If you haven’t used Listen Notes yet , try it now:</p>
<p><a target="_blank" href="https://www.listennotes.com/">https://www.listennotes.com/</a></p>
<h3 id="heading-overview">Overview</h3>
<p>Let’s start with requirements or features of this Listen Notes project.</p>
<p>Listen Notes provides two things to end users:</p>
<ul>
<li>A website <a target="_blank" href="https://www.listennotes.com/">ListenNotes.com</a> for podcast listeners. It provides a search engine, a podcast database, <a target="_blank" href="https://www.listennotes.com/listen/?s=nav">Listen Later</a> playlists, <a target="_blank" href="https://www.listennotes.com/clips/?s=nav">Listen Clips</a> that allows you to cut a segment of any podcast episode, and <a target="_blank" href="https://www.listennotes.com/alerts">Listen Alerts</a> that notifies you when a specified keyword is mentioned in new podcasts on the Internet.</li>
<li><a target="_blank" href="https://www.listennotes.com/api/">Podcast Search &amp; Directory APIs</a> for developers. We need to track the API usage, get money from paid users, do customer support, and more.</li>
</ul>
<p>I run everything on AWS. There are 20 production servers (as of May 5, 2019):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1__HqlSoEW7JEDVnJ9rFSj7w.png" alt="Image" width="600" height="400" loading="lazy">
<em>The servers that run Listen Notes</em></p>
<p>You can easily guess what does each server do from the hostname.</p>
<ul>
<li><strong>production-web</strong> serves web traffics for <a target="_blank" href="https://www.listennotes.com/">ListenNotes.com</a>.</li>
<li><strong>production-api</strong> serves api traffics. We run two versions of API (as of May 4, 2019), thus v1api (the legacy version) and v2api (the new version).</li>
<li><strong>production-db</strong> runs PostgreSQL (primary and replica)</li>
<li><strong>production-es</strong> runs an Elasticsearch cluster.</li>
<li><strong>production-worker</strong> runs offline processing tasks to keep the podcast database always up-to-date and to provide some magical things (e.g., search result ranking, episode/podcast recommendations…).</li>
<li><strong>production-lb</strong> is the load balancer. I also run Redis &amp; RabbitMQ on this server, for convenience. I know this is not ideal. But I’m not a perfect person :)</li>
<li><strong>production-pangu</strong> is the production-like server that I sometimes run one-off scripts and test changes. What’s the meaning of “<a target="_blank" href="https://en.wikipedia.org/wiki/Pangu">pangu</a>”?</li>
</ul>
<p>Most of these servers can be horizontally scaled. That’s why I name them <em>production-something1</em>, <em>production-something2</em>… It could be very easy to add <em>production-something3</em> and <em>production-something4</em> to the fleet.</p>
<h3 id="heading-backend">Backend</h3>
<p>The entire backend is written in Django / Python3. The operating system of choice is Ubuntu.</p>
<p>I use <a target="_blank" href="https://uwsgi-docs.readthedocs.io/en/latest/">uWSGI</a> to serve web traffics. I put <a target="_blank" href="https://www.nginx.com/">NGINX</a> in front of uWSGI processes, which also serves as load balancer.</p>
<p>The main data store is <a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a>, which I’ve got a lot of development &amp; operational experience over many years — battle tested technology is good, so I can sleep well at night. <a target="_blank" href="https://redis.io/">Redis</a> is used for various purposes (e.g., caching, stats,…). It’s not hard to guess that <a target="_blank" href="https://www.elastic.co/">Elasticsearch</a> is used somewhere. Yes, I use Elasticsearch to index podcasts &amp; episodes and to serve search queries, just like <a target="_blank" href="https://medium.com/netflix-techblog/tagged/elasticsearch">most</a> <a target="_blank" href="https://engineeringblog.yelp.com/2017/06/moving-yelps-core-business-search-to-elasticsearch.html">boring</a> <a target="_blank" href="https://eng.uber.com/tag/elasticsearch/">companies</a>.</p>
<p><a target="_blank" href="http://www.celeryproject.org/">Celery</a> is used for offline processing. And <a target="_blank" href="http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html">Celery Beat</a> is for scheduling tasks, which is like Cron jobs but a bit nicer. If in the future Listen Notes gains traction and Celery &amp; Beat cause some scaling issues, I probably will switch to the two projects I did for my previous employer: <a target="_blank" href="https://github.com/Nextdoor/ndkale">ndkale</a> and <a target="_blank" href="https://github.com/Nextdoor/ndscheduler">ndscheduler</a>.</p>
<p><a target="_blank" href="http://supervisord.org/">Supervisord</a> is used for process management on every server.</p>
<p>Wait, how about Docker / Kubernetes / serverless? Nope. As you gain experience, you know when not to over-engineer. I actually did some early Docker work for my previous employer back in 2014, which was good for a mid-sized billion-dollar startup but may be overkill for a one-person tiny startup.</p>
<h3 id="heading-frontend">Frontend</h3>
<p>The web frontend is primarily built with <a target="_blank" href="https://reactjs.org/">React</a> + <a target="_blank" href="https://redux.js.org/">Redux</a> + <a target="_blank" href="https://webpack.js.org/">Webpack</a> + <a target="_blank" href="https://en.wikipedia.org/wiki/ECMAScript">ES</a>. This is pretty standard nowadays. When deploying to production, JS bundles would be uploaded to <a target="_blank" href="https://aws.amazon.com/s3/">Amazon S3</a> and served via <a target="_blank" href="https://aws.amazon.com/cloudfront/">CloudFront</a>.</p>
<p>On <a target="_blank" href="https://www.listennotes.com/">ListenNotes.com</a>, most web pages are half server-side rendered (<a target="_blank" href="https://docs.djangoproject.com/en/2.0/topics/templates/">Django template</a>) and half client-side rendered (<a target="_blank" href="https://reactjs.org/">React</a>). The server-side rendered part provides a boilerplate of a web page, and the client-side rendered part is basically an interactive web app. But a few web pages are rendered entirely via server side, because of my laziness to make things perfect &amp; some potential SEO goodies.</p>
<h4 id="heading-audio-player">Audio player</h4>
<p>I use a heavily modified version of <a target="_blank" href="https://github.com/souporserious/react-media-player">react-media-player</a> to build the audio player on ListenNotes.com, which is used in several places, including <a target="_blank" href="https://www.listennotes.com/p/321dd0ce5b974079bd3fc8d65d132912/">Listen Notes Website</a>, <a target="_blank" href="https://twitter.com/ListenHistoryFM/status/955913550605688832">Twitter embedded player</a>, and embedded player on 3rd party websites:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_R9SqwWtGOKvL0MqOBvsMrA.png" alt="Image" width="600" height="400" loading="lazy">
<em>Embedded player on 3rd party websites</em></p>
<h3 id="heading-podcast-api">Podcast API</h3>
<p>We provide a simple and reliable <a target="_blank" href="https://www.listennotes.com/api/">podcast API</a> to developers. Building the API is similar to building <a target="_blank" href="https://www.listennotes.com/">the website</a>. I use the same Django/Python stack for the backend, and ReactJs for the frontend (e.g., API dashboard, documentation…).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_6s_rx2FAKEJiHy7K6gEwdA.png" alt="Image" width="600" height="400" loading="lazy">
<em>Listen API dashboard</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_dYUinicZH-m6HZPpE1MXBg.png" alt="Image" width="600" height="400" loading="lazy">
<em>Listen API documentation</em></p>
<p>For the API, we need to track how many requests a user use in current billing cycle, and charge $$$ at the end of a billing cycle. It’s not hard to imagine that Redis is heavily used here :)</p>
<h3 id="heading-devops">DevOps</h3>
<h4 id="heading-machine-provisioning-amp-code-deployment">Machine provisioning &amp; code deployment</h4>
<p>I use <a target="_blank" href="http://docs.ansible.com/">Ansible</a> for machine provisioning. Basically, I wrote a bunch of yaml files to specify what type of servers need to have what configuration files &amp; what software. I can spin up a server with all correct configuration files &amp; all software installed with one button push. This is the directory structure of those Ansible yaml files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_oql35nt1Iak2FzniugFPSw.png" alt="Image" width="600" height="400" loading="lazy">
<em>I could’ve done a better job in naming things. But again, it’s good enough for now.</em></p>
<p>I also use Ansible to deploy code to production. Basically, I have a wrapper script <em>deploy.sh</em> that is run on macOS:</p>
<blockquote>
<p><em>./deploy.sh production HEAD web</em></p>
</blockquote>
<p>The deploy.sh script takes three arguments:</p>
<ul>
<li><strong>Environment</strong>: production or staging.</li>
<li><strong>Version of the listennotes repo</strong>: HEAD means “just deploy the latest version”. If a SHA of a git commit is specified, then it’ll deploy a specific version of code — this is particularly useful when I need to rollback from a bad deployment.</li>
<li><strong>What kind of servers</strong>: web, worker, api, or all. I don’t have to deploy to all servers all at once. Sometimes I make changes on Javascript code, then I just need to deploy to web, without touching api or worker.</li>
</ul>
<p>The deployment process is mostly orchestrated by Ansible yaml files, and of course, it’s dead simple:</p>
<ul>
<li><strong>On my Macbook Pro</strong>, if it’s to deploy to web servers, then build Javascript bundles and upload to S3.</li>
<li><strong>On the target servers</strong>, git clone the listennotes repo to a timestamp-named folder, check out the specific version, and pip install new Python dependencies if any.</li>
<li><strong>On the target servers</strong>, switch symlink to the above timestamp-named folder and restart servers via supervisorctl.</li>
</ul>
<p>As you can see, I don’t use those fancy CI tools. Just dead simple things that actually work.</p>
<h4 id="heading-monitoring-amp-alerting">Monitoring &amp; alerting</h4>
<p>I use <a target="_blank" href="https://www.datadoghq.com/">Datadog</a> for monitoring &amp; alerting. I’ve got some high level metrics in a simple dashboard. Whatever I do here is to boost my confidence when I am messing around the production servers.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_nrvlxilaFwNJtZDeGt01fQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>Datadog dashboard for Listen Notes, as of Dec 2017.</em></p>
<p>I connect <a target="_blank" href="https://www.datadoghq.com/">Datadog</a> to PagerDuty. If something goes wrong, <a target="_blank" href="https://www.pagerduty.com/">PagerDuty</a> will send me alerts via phone call &amp; SMS.</p>
<p>I also use <a target="_blank" href="https://rollbar.com/">Rollbar</a> to keep an eye on the health of Django code, which will catch unexpected exceptions and notify me via email &amp; Slack as well.</p>
<p>I use <a target="_blank" href="https://slack.com/">Slack</a> a lot. Yes, this is a one-person company, so I don’t use Slack for communicating with human beings. I use Slack to monitor interesting application-level events. In addition to integrating Datadog and Rollbar with Slack, I also use <a target="_blank" href="https://api.slack.com/incoming-webhooks">Slack incoming webhooks</a> in Listen Notes backend code to notify me whenever a user signs up or performs some interesting actions (e.g., adding or deleting things). This is a very common practice in tech companies. When you read some books about Amazon or PayPal’s early history, you’ll know that both companies had similar notification mechanism: whenever a user signed up, there would be a “ding” sound to notify everyone in the office.</p>
<p>Since launched in early 2017, Listen Notes hasn’t got any big outage (&gt; 5 minutes) except for <a target="_blank" href="https://broadcast.listennotes.com/postmortem-on-apr-22-2018-outage-e5a87723d003">this one</a>. I’m always very careful &amp; practical in these operational stuffs. The web servers are significantly over-provisioned, just in case there’s some huge spike due to press events or whatever.</p>
<h3 id="heading-development">Development</h3>
<p>I work in a <a target="_blank" href="https://refer.wework.com/i/WenbinFang">WeWork</a> coworking space in San Francisco. Some people may wonder why not just work from home or from some random coffee shops. Well, I value productivity a lot and I’m willing to invest money in productivity. I don’t believe piling time helps software development (or any soft of knowledge/creativity work). It’s rare that I work over 8 hours in a day (Sorry, <a target="_blank" href="https://www.nytimes.com/2019/04/29/technology/china-996-jack-ma.html">996 people</a>). I want to make every minute count. Thus, a nice &amp; relatively expensive private office is what I need :) Instead of optimizing for spending more time &amp; saving money, I optimize for spending less time &amp; making money :)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_LqJym-17rqU-vyNzanCiVA.jpeg" alt="Image" width="600" height="400" loading="lazy">
<em>My office at WeWork</em></p>
<p>I’m using a MacBook Pro. I run the (almost) identical infrastructure inside <a target="_blank" href="https://www.vagrantup.com/">Vagrant</a> + <a target="_blank" href="https://www.virtualbox.org/wiki/Downloads">VirtualBox</a>. I use the same set of Ansible yaml files as described above to provision the development environment inside Vagrant.</p>
<p>I subscribe to the <a target="_blank" href="https://danluu.com/monorepo/">monolithic repo</a> philosophy. So there’s one and only one listennotes repo, containing DevOps scripts, frontend &amp; backend code. This listennotes repo is hosted as a GitHub private repo. I do all development work on the main branch. I rarely use feature branches.</p>
<p>I write code and run the dev servers (Django runserver &amp; webpack dev server) by using <a target="_blank" href="https://www.jetbrains.com/pycharm/">PyCharm</a>. Yea, I know, it’s boring. After all, it’s not Visual Studio Code or Atom or whatever cool IDEs. But PyCharm works just fine for me. I’m old school.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/1_0qlv-bne1Ld2wuUxCeDFwQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>My PyCharm</em></p>
<h3 id="heading-miscellaneous">Miscellaneous</h3>
<p>There are a bunch of useful tools &amp; services that I use to build Listen Notes as a product and a company:</p>
<ul>
<li><a target="_blank" href="https://www.iterm2.com/">iTerm2</a> and <a target="_blank" href="https://github.com/tmux/tmux/wiki">tmux</a> for the terminal stuffs.</li>
<li><a target="_blank" href="https://www.notion.so/?r=d1e4526dd2924f4796cd10235cbe132e">Notion</a> for TODO lists, wiki, taking notes, design documents…</li>
<li><a target="_blank" href="https://gsuite.google.com/">G Suite</a> for @listennotes.com email account, calendar, and other Google services.</li>
<li><a target="_blank" href="http://www.mailchimp.com/monkey-rewards/?utm_source=freemium_newsletter&amp;utm_medium=email&amp;utm_campaign=monkey_rewards&amp;aid=da29e56f1e479faf6b4ef3f72&amp;afl=1">MailChimp</a> for sending the <a target="_blank" href="https://us16.campaign-archive.com/home/?u=da29e56f1e479faf6b4ef3f72&amp;id=ba72067923">monthly email newsletter</a>.</li>
<li><a target="_blank" href="https://aws.amazon.com/ses/">Amazon SES</a> for sending transactional &amp; some marketing emails.</li>
<li><a target="_blank" href="https://gusto.com/r/wenbin">Gusto</a> to pay myself and contractors who are not from Upwork.</li>
<li><a target="_blank" href="https://www.upwork.com/">Upwork</a> to find contractors.</li>
<li><a target="_blank" href="https://admanager.google.com/home/">Google Ads Manager</a> to mange direct sales ads and track performance.</li>
<li><a target="_blank" href="https://www.carbonads.net/">Carbon Ads</a> and <a target="_blank" href="https://www.buysellads.com/">BuySellAds</a> for fallback ads.</li>
<li><a target="_blank" href="https://www.cloudflare.com/">Cloudflare</a> for DNS management, CDN, and firewall.</li>
<li><a target="_blank" href="https://zapier.com/">Zapier</a> and <a target="_blank" href="https://trello.com/">Trello</a> to streamline the <a target="_blank" href="https://www.listennotes.com/interviews/">podcaster interview</a> workflow.</li>
<li><a target="_blank" href="https://broadcast.listennotes.com/">Medium</a> for the company blog (obviously).</li>
<li><a target="_blank" href="https://www.godaddy.com/">Godaddy</a> and <a target="_blank" href="https://www.namecheap.com/">Namecheap</a> for domain names.</li>
<li><a target="_blank" href="https://stripe.com/">Stripe</a> for getting money from users (primarily for <a target="_blank" href="https://www.listennotes.com/api/">API</a>).</li>
<li><a target="_blank" href="https://cloud.google.com/speech-to-text/">Google speech-to-text API</a> to transcribe episodes.</li>
<li><a target="_blank" href="https://healthy.kaiserpermanente.org/">Kaiser Permanente</a> for health insurance.</li>
<li><a target="_blank" href="https://atlas.stripe.com/">Stripe Atlas</a> to incorporate Listen Notes, Inc.</li>
<li><a target="_blank" href="https://www.clerky.com/">Clerky</a> to generate legal documents for fund raising (SAFE) and hiring contractors who are not from Upwork.</li>
<li><a target="_blank" href="https://www.referquickbooks.com/s/Wenbin">Quickbooks</a> for bookkeeping.</li>
<li><a target="_blank" href="https://1password.com/">1password</a> to manage login credentials for tons of services.</li>
<li><a target="_blank" href="http://brex.com/signup?rc=oPLQ0ZQ">Brex</a> for charge card — you can get incremental $5000 AWS credits, which can be applied on top of the AWS credits from WeWork or Stripe Atlas.</li>
<li><a target="_blank" href="http://refer.amex.us/WENBIFIUoH?XLINK=MYCP">Bonvoy Business Amex Card</a> — You can earn Marriott Bonvoy points for luxury hotels and flights. It’s the best credit card points for traveling :)</li>
<li><a target="_blank" href="https://www.capitalone.com/small-business-bank/">Capital One Spark</a> for checking account.</li>
</ul>
<h3 id="heading-keep-calm-and-carry-on">Keep calm and carry on…</h3>
<p>As you can see, we are living in a wonderful age to start a company. There are so many off-the-shelf tools and services that save us time &amp; money and increase our productivity. It’s more possible than ever to build something useful to the world with a tiny team (or just one person), using simple &amp; boring technology.</p>
<p>As time goes, companies become smaller and smaller. You don’t need to hire tons of full-time employees. You can hire services (SaaS) and on-demand contractors to get things done.</p>
<p>Most of time, the biggest obstacle of building &amp; shipping things is over thinking. What if this, what if that. Boy, you are not important at all. Everyone is busy in their own life. No one cares about you and the things you build, until you prove that you are worth other people’s attention. Even you screw up the initial product launch, few people will notice. <a target="_blank" href="https://hackernoon.com/think-big-start-small-act-fast-6fdab1f771ea">Think big, start small, act fast</a>. It’s absolutely okay to use the boring technology and start something simple (even ugly), as long as you actually solve problems.</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/wenbinf/status/1082725746160746496?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1082725746160746496&amp;ref_url=https%3A%2F%2Fbroadcast.listennotes.com%2Fmedia%2F622ba96d011f0ebfd9712504b7e353c3%3FpostId%3D56697c2e347b"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p>There are so many <a target="_blank" href="http://stevemcconnell.com/articles/cargo-cult-software-engineering/">cargo-cult</a>-type people now. Ignore the noises. Keep calm and carry on.</p>
<hr>
<p>If you haven’t used Listen Notes yet , try it now:</p>
<p><a target="_blank" href="https://www.listennotes.com/">https://www.listennotes.com/</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Sending emails with Amazon SES ]]>
                </title>
                <description>
                    <![CDATA[ By Kangze Huang The Complete AWS Web Boilerplate — Tutorial 3 Table of Contents Part 0: Introduction to the Complete AWS Web Boilerplate Part 1: User Authentication with AWS Cognito (3 parts) Part 2: Saving File Storage Costs with Amazon S3 (1 part... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/sending-emails-with-amazon-ses-7617e83327b6/</link>
                <guid isPermaLink="false">66c35e79b8711219e1e72dea</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ email marketing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 20 Mar 2017 02:49:59 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*6qHAynt7vd0MQr7Yut0LVA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Kangze Huang</p>
<h4 id="heading-the-complete-aws-web-boilerplate-tutorial-3">The Complete AWS Web Boilerplate — Tutorial 3</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/APBEF5hr2WpoHLy0DEuLKJV2GzmYTubut78Q" alt="Image" width="800" height="283" loading="lazy"></p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<blockquote>
<p><strong>Part 0:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/the-complete-aws-web-boilerplate-d0ca89d1691f#.3eqpvcjsy">Introduction to the Complete AWS Web Boilerplate</a></p>
<p><strong>Part 1:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/user-management-with-aws-cognito-1-3-initial-setup-a1a692a657b3#.cbkz7b2jp">User Authentication with AWS Cognito</a> (3 parts)</p>
<p><strong>Part 2:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/amazon-s3-cloud-file-storage-for-performance-and-cost-savings-8f38d7769619#.l9so2hk00">Saving File Storage Costs with Amazon S3</a> (1 part)</p>
<p><strong>Part 3:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/sending-emails-with-amazon-ses-7617e83327b6#.5nhcrr609">Sending Emails with Amazon SES</a> (1 part)</p>
<p>Part 4: Manage Users and Permissions with AWS IAM <strong>[Coming Soon]</strong></p>
<p>Part 5: Cloud Server Hosting with AWS EC2 and ELB<strong>[Coming Soon]</strong></p>
<p>Part 6: The MongoDB Killer: AWS DynamoDB <strong>[Coming Soon]</strong></p>
<p>Part 7: Painless SQL Scaling using AWS RDS <strong>[Coming Soon]</strong></p>
<p>Part 8: Serverless Architecture with Amazon Lambda <strong>[Coming Soon]</strong></p>
</blockquote>
<p>Download the Github <a target="_blank" href="https://github.com/kangzeroo/Kangzeroos-Complete-AWS-Web-Boilerplate/tree/SES">here</a>.</p>
<h3 id="heading-setup">Setup</h3>
<p>Sending emails with Amazon SES is really straightforward. Let’s start at the set-up. Go to Amazon SES and click <strong>Email Addresses</strong> in the sidebar. Then click <strong>Verify a New Email Address</strong> and enter an email you want to use for messaging in the app.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/-Mx1fdq0M7OF63l3-3Jmc0fQHrLl8jR56nbh" alt="Image" width="800" height="214" loading="lazy"></p>
<p>Now go to your email provider and click the verification link. After verifying, go back to Amazon SES <strong>Email Addresses</strong> tab. You should now see your email verified.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/KgdxVyP4QlP-XpKRFG-aUpjn2Wuj5VH1-gSC" alt="Image" width="800" height="230" loading="lazy"></p>
<p>This was necessary for 2 reasons. First is that we need an email for sending a message, and the second is because we are in a sandbox environment. A sandbox environment means you can only send and receive emails from verified addresses, and prevents you from spamming people. This is all the set-up we need for this boilerplate.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/wRQoOk9N4QQSCM6Slimww-YrZhMrWzi2-KWR" alt="Image" width="800" height="498" loading="lazy"></p>
<p>If you want be able to send emails to any email address, you need to make a written request to Amazon to graduate from the sandbox environment. To do this, navigate to the top-right hand corner at <strong>Support</strong> &amp;<strong>gt; Support Cen</strong>ter.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ErZNvtZKAWPsUVrhSHje-JmjlseahbCJhi1V" alt="Image" width="800" height="174" loading="lazy"></p>
<p>At this next screen, click <code>Create case</code>.</p>
<p>This is a straightforward form, but we’ll briefly explain. Select <strong>Service Limit Increase</strong> and set the <strong>Limit Type</strong> to <strong>SES Sending Limits</strong>. Now create 2 requests, one where <strong>Limit</strong> is <strong>Desired Daily Sending Quota</strong> (how many emails can be sent in one day), and the other where <strong>Limit</strong> is <strong>Desired Maximum Send Rate</strong>. Set the <strong>New limit value</strong> to the amount that you need. Finally, optionally set the Mail Type as it increases you odds of approval. Use transactional if your emails are generated as a request of a user’s activity. There are others available for other use cases.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/FqkYQdVUxsfCm232bHtQlCbCNltKdDoh57p8" alt="Image" width="800" height="600" loading="lazy"></p>
<p>The rest of the request is easy. Make sure you agree to comply with the Terms of Service, and you have a process to handle <a target="_blank" href="http://docs.aws.amazon.com/ses/latest/DeveloperGuide/best-practices-bounces-complaints.html">bounces and complaints</a> (for when users mark your email as spam). Finally, give a brief explanation of your use case.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/in92mh4YL5eNVCjDz6Iy96b7jhWP3xZ222Fv" alt="Image" width="800" height="568" loading="lazy"></p>
<p>Submit your code and you should get an email from Amazon with the results of your service increase request. Once approved, your app can send messages to any email.</p>
<h3 id="heading-the-code">The Code</h3>
<p>We’re ready to dig into the code! Go to <code>App/src/api/aws/aws_ses.js</code> where the bulk of the code resides. Let’s take a look at the main function <code>sendAWSEmail()</code>:</p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendAWSEmail</span>(<span class="hljs-params">email, message</span>)</span>{ <span class="hljs-keyword">const</span> ses = <span class="hljs-keyword">new</span> AWS.SES({  <span class="hljs-attr">region</span>: <span class="hljs-string">'us-east-1'</span> }) <span class="hljs-keyword">const</span> p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">res, rej</span>)=&gt;</span>{  <span class="hljs-keyword">if</span>(!email|| message){   rej(<span class="hljs-string">'Missing user email or message content'</span>)  }<span class="hljs-keyword">else</span>{   <span class="hljs-keyword">const</span> params = createInquiryParamsConfig(email, message)   <span class="hljs-comment">// console.log('Sending email with attached params!')   AWS.config.credentials.refresh(function(){    // console.log(AWS.config.credentials)    ses.sendEmail(params, function(err, data) {      if(err){        // console.log(err, err.stack); // an error occurred        rej(err)      }else{       // console.log(data);           // successful response     res('Success! Email sent')      }    })   })  } }) return p}</span>
</code></pre><p>This is extremely straightforward. We receive two arguments, an email to send to, and a message to be sent. The first thing we do in this function is instantiate the AWS SES object for interacting with AWS by simply passing in the region. Next we check if there is a recipient email and a message. If both are provided, then we can actually send the email.</p>
<p>Assuming we have both a recipient email and message, we will create a <code>params</code> object for AWS SES to read for all the info &amp; options necessary. This <code>params</code> object is created with <code>createInquiryParamsConfig()</code>. Before we dive into that rabbit hole, let’s just quickly finish up explaining the rest of <code>sendAWSEmail()</code>. We refresh AWS Cognito user’s credentials (that we set with AWS Cognito, explained in my <a target="_blank" href="https://medium.com/@kangzeroo/user-management-with-aws-cognito-1-3-initial-setup-a1a692a657b3#.ykdx6xqx2">other tutorial</a>) and call <code>ses.sendEmail</code> with <code>params</code> and a response callback passed in. Reject the promise if there is an error, and resolve with a success message if there is no error. <code>ses.sendEmail</code> is the only AWS function we will use, and everything else is we need is determined in <code>params</code>.</p>
<p>Now let’s see how to make <code>params</code> with <code>createInquiryParamsConfig()</code>.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createInquiryParamsConfig</span>(<span class="hljs-params">email, message</span>)</span>{ <span class="hljs-keyword">const</span> params = {   <span class="hljs-attr">Destination</span>: {      <span class="hljs-attr">BccAddresses</span>: [],     <span class="hljs-attr">CcAddresses</span>: [],     <span class="hljs-attr">ToAddresses</span>: [ email ]   },   <span class="hljs-attr">Message</span>: {      <span class="hljs-attr">Body</span>: {        <span class="hljs-attr">Html</span>: {         <span class="hljs-attr">Data</span>: generateHTMLInquiryEmail(landlordEmail, message),         <span class="hljs-attr">Charset</span>: <span class="hljs-string">'UTF-8'</span>       }     },     <span class="hljs-attr">Subject</span>: {        <span class="hljs-attr">Data</span>: <span class="hljs-string">'Kangzeroos Boilerplate says hi '</span> + email,       <span class="hljs-attr">Charset</span>: <span class="hljs-string">'UTF-8'</span>     }   },   <span class="hljs-attr">Source</span>: <span class="hljs-string">'yourApp@gmail.com'</span>,    <span class="hljs-attr">ReplyToAddresses</span>: [ <span class="hljs-string">'yourApp@gmail.com'</span> ],   <span class="hljs-attr">ReturnPath</span>: <span class="hljs-string">'yourApp@gmail.com'</span> } <span class="hljs-keyword">return</span> params}
</code></pre><p>Pretty straightforward, we pass in <code>email</code> and <code>message</code>, and return a big javascript object. All the values you see here are necessary, but you can add a ton of other optional configurations too. The function we must pay attention to is <code>generateHTMLInquiryEmail()</code>. Let’s look at that.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateHTMLInquiryEmail</span>(<span class="hljs-params">email, message</span>)</span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">`  &lt;!DOCTYPE html&gt;  &lt;html&gt;    &lt;head&gt;      &lt;meta charset='UTF-8' /&gt;      &lt;title&gt;title&lt;/title&gt;    &lt;/head&gt;    &lt;body&gt;     &lt;table border='0' cellpadding='0' cellspacing='0' height='100%' width='100%' id='bodyTable'&gt;      &lt;tr&gt;          &lt;td align='center' valign='top'&gt;              &lt;table border='0' cellpadding='20' cellspacing='0' width='600' id='emailContainer'&gt;                  &lt;tr style='background-color:#99ccff;'&gt;                      &lt;td align='center' valign='top'&gt;                          &lt;table border='0' cellpadding='20' cellspacing='0' width='100%' id='emailBody'&gt;                              &lt;tr&gt;                                  &lt;td align='center' valign='top' style='color:#337ab7;'&gt;                                      &lt;h3&gt;<span class="hljs-subst">${message}</span>&lt;/h3&gt;                                  &lt;/td&gt;                              &lt;/tr&gt;                          &lt;/table&gt;                      &lt;/td&gt;                  &lt;/tr&gt;                  &lt;tr style='background-color:#74a9d8;'&gt;                      &lt;td align='center' valign='top'&gt;                          &lt;table border='0' cellpadding='20' cellspacing='0' width='100%' id='emailReply'&gt;                              &lt;tr style='font-size: 1.2rem'&gt;                                  &lt;td align='center' valign='top'&gt;                                      &lt;span style='color:#286090; font-weight:bold;'&gt;Send From:&lt;/span&gt; &lt;br/&gt; <span class="hljs-subst">${email}</span>                                  &lt;/td&gt;                              &lt;/tr&gt;                          &lt;/table&gt;                      &lt;/td&gt;                  &lt;/tr&gt;              &lt;/table&gt;          &lt;/td&gt;      &lt;/tr&gt;      &lt;/table&gt;    &lt;/body&gt;  &lt;/html&gt; `</span>}
</code></pre><p>All we are doing here is creating an HTML file and passing in the <code>email</code> and <code>message</code> to create a custom email. We use ES6 string literals to add in string variables with <code>${ }</code> like so: <code>&lt;h3&gt;${message</code>}.</p>
<p>And that’s it! You can use whatever front end code you want, simply pass in an <code>email</code> and <code>message</code> to <code>sendAWSEmail()</code>. Just remember <code>sendAWSEmail()</code> returns a promise, so you will have to handle that accordingly. If you don’t know how to handle promises, check out my <a target="_blank" href="https://medium.com/@kangzeroo/quick-story-about-javascript-promises-31b4e76ed0cd#.sty9l0ncx">other tutorial here</a>.</p>
<p>See you next time!</p>
<h3 id="heading-table-of-contents-1">Table of Contents</h3>
<blockquote>
<p><strong>Part 0:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/the-complete-aws-web-boilerplate-d0ca89d1691f#.3eqpvcjsy">Introduction to the Complete AWS Web Boilerplate</a></p>
<p><strong>Part 1:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/user-management-with-aws-cognito-1-3-initial-setup-a1a692a657b3#.cbkz7b2jp">User Authentication with AWS Cognito</a> (3 parts)</p>
<p><strong>Part 2:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/amazon-s3-cloud-file-storage-for-performance-and-cost-savings-8f38d7769619#.l9so2hk00">Saving File Storage Costs with Amazon S3</a> (1 part)</p>
<p><strong>Part 3:</strong> <a target="_blank" href="https://medium.com/@kangzeroo/sending-emails-with-amazon-ses-7617e83327b6#.5nhcrr609">Sending Emails with Amazon SES</a> (1 part)</p>
<p>Part 4: Manage Users and Permissions with AWS IAM <strong>[Coming Soon]</strong></p>
<p>Part 5: Cloud Server Hosting with AWS EC2 and ELB<strong>[Coming Soon]</strong></p>
<p>Part 6: The MongoDB Killer: AWS DynamoDB <strong>[Coming Soon]</strong></p>
<p>Part 7: Painless SQL Scaling using AWS RDS <strong>[Coming Soon]</strong></p>
<p>Part 8: Serverless Architecture with Amazon Lambda <strong>[Coming Soon]</strong></p>
</blockquote>
<p>This method was partially used in the deployment of <a target="_blank" href="http://renthero.ca">renthero.ca</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The State Of JavaScript 2016: Results ]]>
                </title>
                <description>
                    <![CDATA[ By Sacha Greif The Wait Is Over I just looked through my inbox, and found a receipt for the awesome React for Beginners course dated November 4, 2015. So it’s been almost one full year since I ventured into the Wild West of modern JavaScript developm... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-state-of-javascript-2016-results-4beb4ff06961/</link>
                <guid isPermaLink="false">66c362785731bdf41f8c644f</guid>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 03 Oct 2016 14:24:20 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*BiVvD3saeQQ5EgEBvrm6xg.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sacha Greif</p>
<h4 id="heading-the-wait-is-over">The Wait Is Over</h4>
<p>I just looked through my inbox, and found a receipt for the awesome <a target="_blank" href="https://reactforbeginners.com/friend/STATEOFJS">React for Beginners</a> course dated November 4, 2015. So it’s been almost one full year since I ventured into the Wild West of modern JavaScript development.</p>
<p>I’m now fairly confident in my React skills, but it seems like as soon as I master one challenge, another one pops up: should I use <a target="_blank" href="http://redux.js.org/">Redux</a>? Or maybe look into <a target="_blank" href="http://vuejs.org/">Vue</a> instead? Or go full-functional and jump on the <a target="_blank" href="http://elm-lang.org/">Elm</a> bandwagon?</p>
<p>I knew I couldn’t be the only one with these question, so I decided to launch the <a target="_blank" href="http://stateofjs.com">State of JavaScript</a> survey to get a more general picture of the ecosystem. Turns out I hit a nerve: within a week, I had accumulated <strong>over 9000</strong> responses (no meme intended)!</p>
<p>It took me a while to go through the data, but the results are finally live!</p>
<h4 id="heading-check-out-the-survey-results-herehttpstateofjscom"><a target="_blank" href="http://stateofjs.com/">Check out the survey results here</a></h4>
<p>And if you’d like to know a little bit more about the whole enterprise, just read on.</p>
<h3 id="heading-analyzing-the-data">Analyzing the Data</h3>
<p>You might be wondering why it took me so long to analyze and publish the data. Hopefully this will become clear when you read through the report.</p>
<p>I didn’t want to simply publish a bunch of charts with no context. Raw stats are great if you already know what you’re looking for, but if you’re looking for guidance then they can just as well add to the overall noise.</p>
<p>Instead, I decided to use these stats as a basis for a detailed report on the current state of JavaScript.</p>
<h3 id="heading-the-authors">The Authors</h3>
<p>I was originally planning on writing the whole thing myself, but I quickly realized that A) this would be <em>a lot</em> of work and B) I didn’t want the report to be too biased by my own preconceptions.</p>
<p>So I asked a few developer friends to pitch in and write the various sections of the report. Not only is the overall report a lot more objective –and interesting– as a result, but I was also able to get experts for each topic (I’ll be the first to admit that there are entire swathes of the JavaScript world I know little about).</p>
<p>So a huge thank to all the authors who contributed to the report: <a target="_blank" href="https://twitter.com/tmeasday">Tom Coleman</a>, <a target="_blank" href="http://michaelrambeau.com/">Michael Rambeau</a>, <a target="_blank" href="https://medium.com/@shilman">Michael Shilman</a>, <a target="_blank" href="https://twitter.com/arunoda">Arunoda Susiripala</a>, <a target="_blank" href="http://mochimachine.org/">Jennifer Wong</a>, and <a target="_blank" href="http://joshowens.me/">Josh Owens</a>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/49Uk22SyawwAbzFqgUs26BiLSJlx-qhwznMo" alt="Image" width="800" height="107" loading="lazy"></p>
<h3 id="heading-the-charts">The Charts</h3>
<p>Here’s a little more info about the main types of chart you’ll see throughout the survey.</p>
<h4 id="heading-stacked-bar-chart">Stacked Bar Chart</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/yN22NPK31J0d8BCyMYP4NntgMat2HzcvuUvl" alt="Image" width="690" height="561" loading="lazy"></p>
<p>This is the main chart for each section. For each technology, it shows the breakdown of developers who <strong>have never heard of it</strong>, have heard of it but <strong>aren’t interested</strong>/<strong>want to learn it</strong>, and have used it and <strong>would not</strong>/<strong>would use it again</strong>.</p>
<p>You can toggle between percents and absolute numbers as well as filter by interest or satisfaction. But note that when filtering, the percentages are relative to the currently selected value pair (in other words both numbers total 100%).</p>
<h4 id="heading-heatmap">Heatmap</h4>
<p>I also wanted to explore the correlations <em>between</em> each technology.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/mRUJJ8RAVe9k37oXsmRnmha9U0-IzRh0JZp8" alt="Image" width="800" height="326" loading="lazy"></p>
<p>The heatmap charts achieve this by showing you how likely someone who uses one technology (defined as having selected “I have used X and would use it again”) is to use another technology, compared to the average.</p>
<p>Pink means very likely, blue means very unlikely. In other words, a deep pink tile in the React row and Redux column means “React users are a lot more likely than average to also use Redux”.</p>
<h3 id="heading-built-with">Built With</h3>
<p>I decided to practice what I preached and build the survey app itself using modern JavaScript tools, namely React powered by the excellent <a target="_blank" href="https://github.com/gatsbyjs/gatsby">Gatsby</a> static site generator.</p>
<p>It might seem weird at first to use React for what is essentially a static HTML page, but it turns out this brings a ton of advantages: for example, you’re able to use React’s vast ecosystem of modules such as the great <a target="_blank" href="http://recharts.org">Recharts</a> library.</p>
<p>In fact I believe this may just prove to be a new, <em>better</em> approach for developing static sites, and I hope to write a more detailed post about it soon.</p>
<h3 id="heading-partners">Partners</h3>
<p>Finally, I wouldn’t have been able to take a month off to work on this without financial support from some really cool people.</p>
<p>Both Wes Bos (who has put out the afore-mentioned <a target="_blank" href="https://reactforbeginners.com/friend/STATEOFJS">React for Beginners</a> as well as the new <a target="_blank" href="https://es6.io/friend/stateofjs">ES6 for Everybody</a>) and <a target="_blank" href="http://egghead.io">egghead.io</a> (which in my opinion is the single best resource out there for learning cutting-edge JavaScript development) accepted to sponsor the project. Thanks guys!</p>
<h3 id="heading-support-the-project">Support the Project</h3>
<p>If you think what I’ve done here is valuable and would like to support the project, a tweet or share would be much appreciated!</p>
<ul>
<li><a target="_blank" href="https://twitter.com/intent/tweet/?text=The%20State%20Of%20JavaScript%3A%20discover%20the%20most%20popular%20JavaScript%20technologies%20http%3A%2F%2Fstateofjs.com%20%23stateofjs">Tweet</a></li>
<li><a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fstateofjs.com">Share on Facebook</a></li>
</ul>
<p>Additionally, you can also contribute a donation to <a target="_blank" href="https://gumroad.com/l/hLWTB">get access to the raw anonymized data</a> (or just enter “0” to get it for free).</p>
<h3 id="heading-whats-next">What’s Next</h3>
<p>Now that the survey is over and we all know what the best technologies are, hopefully we can put any talks of “JavaScript fatigue” or “endless churn” to rest and move on with our programming lives.</p>
<p>Haha, as if!</p>
<p>If one thing has become clear to me, it’s that the growing pains that JavaScript is going through right now are only the beginning. While React has barely emerged as the victor of the Front-End Wars of 2015, some developers are already decrying React for not being functional enough, and embracing Elm or ClojureScript instead.</p>
<p>In other words, my job here isn’t done, and I fully intend to do this survey again next year! If you want to be notified when that happens, I encourage you to <a target="_blank" href="http://eepurl.com/ccyxCn">leave me your email here</a>.</p>
<p>Until then, I can only hope these survey results will provide a little clarity in our never-ending quest to make sense of the JavaScript ecosystem!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Hard-Earned Android Programming Experiences ]]>
                </title>
                <description>
                    <![CDATA[ By Arun (now voidmain.dev) This post, like Kent Beck says in his book Implementation Patterns, “…is based on a rather fragile premise that good code matters…”. But we all know that clean code matters as we’ve had to deal for so long with its lack. An... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/hard-earned-android-programming-experiences-361fbaaecd07/</link>
                <guid isPermaLink="false">66c34c1e93db2451bd44146e</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ androiddev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 07 Jul 2016 15:42:18 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*5v2uWjgpnjKka7rhTyxzmA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Arun (now voidmain.dev)</p>
<p>This post, like Kent Beck says in his book <strong>Implementation Patterns</strong><em>, “…is based on a rather fragile premise that good code matters…”.</em> But we all know that clean code matters as we’ve had to deal for so long with its lack. And so does Kent.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/D1y48LxFVLN-7DJq2e-GSt4r868LL7H45DCS" alt="Image" width="100" height="146" loading="lazy">
<em>Kent Beck</em></p>
<h4 id="heading-the-total-cost-of-owning-a-mess">The Total Cost of Owning a Mess</h4>
<p>A few years ago, like every naive Android developer working in an early-stage startup in India, I tried to “<em>hack”</em> real world problems, to “<em>disrupt the industry”</em> and to <em>put a “dent in the universe”</em>. Without a care in the world about good software design or architecture, I started writing code to build an Android app that would one day become one of the biggest consumer heath-care apps in India.</p>
<p>Sprint after sprint, hack after hack, features were built in a mad rush. <em>Build. Measure. Learn</em>. <em>Time-to-market</em> was important and every day mattered. Time flew by, we were growing at the rate of 1 team member every 6 months and the app had hit the million downloads mark.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/hLHerHSI0WHhoNSBZ8iTKX7KjbmvHDmEv90H" alt="Image" width="646" height="209" loading="lazy">
<em>Our app’s Google Play store downloads and rating.</em></p>
<p>By this time, the app had stopped being trivial and it had become a <em>multi-tenant</em> client, if that’s even a thing. Features that would take hours when we started now took days, sometimes weeks. Every Activity was 1000+ lines of spaghetti code as Android inherently doesn’t worry too much about the separation of concerns. <strong>The total cost of owning a mess had significantly slowed us down.</strong></p>
<h4 id="heading-the-android-conundrum">The Android Conundrum</h4>
<p>The code looked ugly, <em>Activities</em> managed everything:</p>
<ul>
<li><em>Threading</em></li>
<li><em>I/O</em></li>
<li><em>Computation</em></li>
<li><em>Layouts</em></li>
<li><em>Config changes</em></li>
<li><em>What not</em></li>
</ul>
<p>After all, <em>Activities</em> are <em>Controllers</em>, right? Or are they <em>Views</em>? I didn’t know anymore.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/DmDdWfbf3wRAQaclKUYmFWXXcgEOZsrDOh2L" alt="Image" width="576" height="256" loading="lazy">
<em>MVC</em></p>
<h4 id="heading-the-grand-redesign-in-the-sky">The Grand Redesign In The Sky</h4>
<p>We needed to design the app in a way that changing a line of code somewhere did not break something somewhere else. The app had to be, as Uncle Bob says, <em>“robust but not rigid, flexible but not fragile”</em>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/IfUwvie1Hd9-7z1AqmI7VATBu3Mct0kjXXvE" alt="Image" width="100" height="116" loading="lazy">
<em>Robert “Uncle Bob” Martin</em></p>
<p>This was when my mentor and friend <a target="_blank" href="https://www.freecodecamp.org/news/hard-earned-android-programming-experiences-361fbaaecd07/undefined">Kashif Razzaqui</a> joined the team to help us alleviate the mess. The grand redesign never happened, but we refactored the hell out of our code:</p>
<ul>
<li>We added a “<em>service”</em> layer and moved all the non-UI code into them, one service at a time.</li>
<li>We chucked <em>AsyncTasks</em> and moved to <em>ListenableFutures</em> using <em>Guava</em>.</li>
<li>We dumped <em>AsyncHttpClient</em> for <em>OkHttp.</em></li>
<li>But more importantly, we started reading a lot: Clean Code, Clean Architecture, SOLID, DRY, The Pragmatic Programmer, Java Concurrency In Practice, Domain Driven Design, etc.</li>
</ul>
<p>Soon we started seeing the benefits of our efforts. Productivity increased, we were writing things faster, everyone was happy.</p>
<p>This was until we unified our apps and all hell broke lose. Just having an additional <em>service</em> layer didn’t cut it.</p>
<h4 id="heading-the-art-of-clean-code">The Art of Clean Code</h4>
<p>After watching Uncle Bob’s videos on <a target="_blank" href="https://www.youtube.com/results?search_query=clean+architecture&amp;page=&amp;utm_source=opensearch">Clean Architecture</a> multiple times and reading a lot on Android app architecture, I decided to experiment with the <a target="_blank" href="https://github.com/esoxjem/MovieGuide">MVP design pattern and RxJava</a>.</p>
<p>A few days into the experimentation, we decided to switch to <em>RxJava</em> and implement <em>MVP using Clean Architecture</em>. We made sure we encapsulated all layers behind interfaces and separated concerns well.</p>
<ul>
<li><em>The View,</em> usually implemented by a Fragment, contains a reference to the presenter. The only thing that the view will do is to call a method from the Presenter every time there is an interface action.</li>
<li><em>The Presenter</em> is responsible to act as the middle man between <em>View</em> and <em>Model</em>. It retrieves data from the Model and returns it formatted to the View. But unlike the typical MVC, it also decides what happens when you interact with the View.</li>
<li><em>The Model</em> is only the gateway to the <em>domain</em> <em>layer</em> or <em>business logic.</em></li>
<li><em>The Interactor</em> deals with I/O and is the provider of data to be displayed in the <em>View</em>.</li>
</ul>
<p>Now it’s much easier to switch out one layer with a completely new implementation. Redesigning the UI, a part and parcel of Android app development, has become much easier. Things can finally move fast without breaking.</p>
<h4 id="heading-the-boy-scout-rule">The Boy Scout Rule</h4>
<p>It’s not enough to write code well, the code has to be <strong>kept clean</strong> over time. The fact of life is that software has a tendency for <em>entropy</em>. We’ve all seen code rot and degrade over time so we borrowed the simple boy scouts rule: “<em>Leave the campground cleaner than you found it.”</em></p>
<p>If we all checked-in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanup doesn’t have to be something big. Change one variable name for the better, break up one function that’s a little too large, eliminate one small bit of duplication, clean up one composite if statement.</p>
<h4 id="heading-conclusion">Conclusion</h4>
<p>Our way of building a scalable app might not be “<em>correct</em>” and you might not agree with this post. After all, not all martial artists agree about the best martial art, or the best technique within one ;)</p>
<p>There are many different approaches towards MVP and a lot of interesting solutions to adapt it to Android. The one fact that we can’t deny is that <em>Clean Code</em> matters and you just can’t sweep it under a rug.</p>
<p>This post borrows heavily from Uncle Bob’s <strong>Clean Code</strong> and steals the title from <a target="_blank" href="https://www.youtube.com/watch?v=dauMw_Bns0w"><em>Kashif’s Droidcon talk</em></a> from 2011.</p>
<p>If <em>Clean Code</em> matters to you, let’s chat :)<br>_Twitter: <a target="_blank" href="http://twitter.com/_arunsasi">@_arunsasi</a>_<br><em>LinkedIn:</em> <a target="_blank" href="https://www.linkedin.com/in/arunsasidharan">https://www.linkedin.com/in/arunsasidharan</a></p>
<h3 id="heading-if-you-liked-this-post-please-hit-the-little-heart">If you liked this post, please hit the little heart! ❤</h3>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
