<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ursula Okafo</title>
    <description>The latest articles on DEV Community by Ursula Okafo (@ursulaonyi).</description>
    <link>https://dev.to/ursulaonyi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2782769%2F632eeee0-867c-46c1-a0b2-a7598c89c93c.jpg</url>
      <title>DEV Community: Ursula Okafo</title>
      <link>https://dev.to/ursulaonyi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ursulaonyi"/>
    <language>en</language>
    <item>
      <title>Building an AI News Digest Agent with Mastra and Telex.im</title>
      <dc:creator>Ursula Okafo</dc:creator>
      <pubDate>Fri, 07 Nov 2025 22:47:44 +0000</pubDate>
      <link>https://dev.to/ursulaonyi/building-an-ai-news-digest-agent-with-mastra-and-telexim-3p1h</link>
      <guid>https://dev.to/ursulaonyi/building-an-ai-news-digest-agent-with-mastra-and-telexim-3p1h</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a real-time news aggregation agent, integrated it with Telex.im, and learned about AI agent architecture along the way.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;For the HNG Stage 3 backend challenge, I set out to build something practical: an AI agent that delivers fresh news headlines on demand. The result? &lt;strong&gt;Daily Headline Digest Agent&lt;/strong&gt; — an intelligent system that fetches trending news, summarizes it with AI, and integrates seamlessly with Telex.im.&lt;/p&gt;

&lt;p&gt;This blog post walks through my journey: why I chose Mastra, how I built the agent, the integration challenges I faced, and what I learned about building AI systems at scale.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Daily Headline Digest Agent&lt;/strong&gt; is an AI-powered news assistant that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetches real-time news from the GNews API across multiple categories (tech, business, sports, health, entertainment, science)&lt;/li&gt;
&lt;li&gt;Supports country-specific news (Nigeria, USA, UK, Canada, Australia, India, and more)&lt;/li&gt;
&lt;li&gt;Uses Google's Gemini 2.0 Flash model for intelligent summarization&lt;/li&gt;
&lt;li&gt;Integrates with Telex.im for seamless workplace collaboration&lt;/li&gt;
&lt;li&gt;Provides formatted, emoji-enhanced news digests with source attribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example interaction:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Give me today's tech news"
Agent: "🗞️ Tech Headlines - Friday, November 7, 2025
1️⃣ OnePlus 15 launching in India on November 13...
2️⃣ iOS 26 Marks a New Divide in the iPhone Lineup..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Mastra?
&lt;/h2&gt;

&lt;p&gt;When I started this project, I had three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build from scratch with raw APIs&lt;/li&gt;
&lt;li&gt;Use LangChain/LlamaIndex&lt;/li&gt;
&lt;li&gt;Use Mastra&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose &lt;strong&gt;Mastra&lt;/strong&gt; for several reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Built for Telex Integration&lt;/strong&gt;&lt;br&gt;
Mastra has first-class support for Telex.im through the A2A protocol. This wasn't a hack — it was designed for this use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Simpler Agent Definition&lt;/strong&gt;&lt;br&gt;
With Mastra, defining an agent is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newsDigestAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newsDigestAgent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;google&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.0-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetchNews&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No complex prompt engineering needed. Clear, declarative syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Type Safety&lt;/strong&gt;&lt;br&gt;
Being a TypeScript-first framework, Mastra gave me confidence with full type checking. My tools had validated schemas using Zod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newsToolSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;maxArticles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Built-in Server/Routing&lt;/strong&gt;&lt;br&gt;
Mastra handles HTTP routing, JSON-RPC validation, and API structure automatically. I didn't have to write boilerplate.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Here's how the system is structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── mastra/
│   ├── agents/
│   │   └── news-agent.ts          # Main AI agent
│   ├── tools/
│   │   └── news-tools.ts          # GNews API integration
│   └── index.ts                   # Mastra instance
├── routes/
│   └── a2a-agent-route.ts         # Telex A2A protocol handler
├── index.ts                       # Express server
└── types/
    └── a2a.types.ts              # Protocol types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Components:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;newsDigestAgent&lt;/strong&gt; - The AI agent that understands natural language and decides when to use tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fetchNews&lt;/strong&gt; - A tool that fetches news from GNews API with smart topic mapping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a2aAgentRoute&lt;/strong&gt; - Handles JSON-RPC 2.0 requests from Telex.im&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express Server&lt;/strong&gt; - Hosts everything on Railway&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Integration Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Local Development
&lt;/h3&gt;

&lt;p&gt;I started by building the agent locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mastra/core @ai-sdk/google dotenv
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Mastra dev server gave me a Studio interface to test the agent instantly. This was powerful — I could iterate on instructions and see results in real-time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Deployment Challenge
&lt;/h3&gt;

&lt;p&gt;This is where I hit my first major hurdle: &lt;strong&gt;Node.js version compatibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My dependencies required Node 20+, but Railway was deploying Node 18. The error was cryptic at first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERR_MODULE_NOT_FOUND: Cannot find module '/app/dist/mastra/agents/news-agent'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Create a &lt;code&gt;.node-version&lt;/code&gt; file to specify Node.js 20:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"20"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .node-version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Always verify your runtime environment. A simple version mismatch can cause confusing module resolution errors with ES modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: ES Module Path Issues
&lt;/h3&gt;

&lt;p&gt;Another challenge: ES modules require explicit file extensions in imports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (broken):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newsDigestAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./agents/news-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (working):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newsDigestAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./agents/news-agent.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a Node.js ES module requirement, not specific to Mastra, but it's easy to miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Environment Variables
&lt;/h3&gt;

&lt;p&gt;My biggest debugging headache was environment variables not being passed to the deployed container.&lt;/p&gt;

&lt;p&gt;Initially, I set them via Railway CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;railway variables &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;GOOGLE_GENERATIVE_AI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But they didn't persist properly. The fix was to redeploy and verify they appeared in the logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Always check &lt;code&gt;railway logs&lt;/code&gt; after setting environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 5: A2A Protocol Implementation
&lt;/h3&gt;

&lt;p&gt;The real integration work came with implementing the A2A (Agent-to-Agent) protocol for Telex.&lt;/p&gt;

&lt;p&gt;Telex expects JSON-RPC 2.0 format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message/send"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your message"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built a dedicated route handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a2aAgentRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;registerApiRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/a2a/agent/:agentId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate JSON-RPC 2.0&lt;/span&gt;
    &lt;span class="c1"&gt;// Extract message&lt;/span&gt;
    &lt;span class="c1"&gt;// Call agent&lt;/span&gt;
    &lt;span class="c1"&gt;// Return formatted response&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key challenge:&lt;/strong&gt; Handling multiple message formats. Telex might send different structures depending on context. My solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Worked Well
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Mastra's Structure&lt;/strong&gt; - The framework enforces good patterns without feeling restrictive&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Tool System&lt;/strong&gt; - Defining tools with Zod schemas is clean and type-safe&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Model Flexibility&lt;/strong&gt; - Easy to switch between Google, OpenAI, Anthropic models&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Railway Deployment&lt;/strong&gt; - Once environment issues were solved, deployments were smooth&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;GNews API&lt;/strong&gt; - Free, reliable news data with good coverage across categories and countries&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Tricky
&lt;/h2&gt;

&lt;p&gt;❌ &lt;strong&gt;Telex Integration Documentation&lt;/strong&gt; - Limited docs on custom A2A implementations (Telex seems designed primarily for Mastra Cloud)&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Module Resolution&lt;/strong&gt; - ES modules require explicit extensions and proper tsconfig settings&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Environment Variables&lt;/strong&gt; - Railroad between CLI setup and deployment wasn't intuitive&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Debugging A2A Failures&lt;/strong&gt; - Hard to know exactly what Telex was sending without better error messages&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Start with Local Testing&lt;/strong&gt;&lt;br&gt;
Always validate your agent works locally before deploying. The Mastra Studio is invaluable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Read the Logs&lt;/strong&gt;&lt;br&gt;
Railway logs showed exactly what was failing. "No service linked" → "Missing environment variables" → "Module not found". The logs told the story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Type Safety Saves Time&lt;/strong&gt;&lt;br&gt;
TypeScript + Zod caught errors before runtime. Schema validation prevented bad data from reaching the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Keep the Agent Instructions Clear&lt;/strong&gt;&lt;br&gt;
I spent time crafting detailed agent instructions with examples. This actually improved the agent's behavior significantly. Good instructions &amp;gt; complex prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Handle Multiple Input Formats&lt;/strong&gt;&lt;br&gt;
Real-world integrations are messy. Building flexibility into message parsing was essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;You can find the complete implementation here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Ursulaonyi/news-digest-agent" rel="noopener noreferrer"&gt;https://github.com/Ursulaonyi/news-digest-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployed API:&lt;/strong&gt; &lt;a href="https://daily-headline-digest-production.up.railway.app" rel="noopener noreferrer"&gt;https://daily-headline-digest-production.up.railway.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Check:&lt;/strong&gt; &lt;a href="https://daily-headline-digest-production.up.railway.app/health" rel="noopener noreferrer"&gt;https://daily-headline-digest-production.up.railway.app/health&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Endpoint:&lt;/strong&gt; &lt;code&gt;POST /api/chat&lt;/code&gt; with &lt;code&gt;{"message": "your query"}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Possible improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add caching to reduce API calls&lt;/li&gt;
&lt;li&gt;Implement sentiment analysis on headlines&lt;/li&gt;
&lt;li&gt;Add multi-language support&lt;/li&gt;
&lt;li&gt;Create a web UI for the agent&lt;/li&gt;
&lt;li&gt;Build a scheduled digest for Slack integration&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Building the Daily Headline Digest Agent taught me that modern AI agent frameworks like Mastra handle a lot of complexity beautifully. The hardest parts weren't about AI — they were about infrastructure, environment setup, and protocol integration.&lt;/p&gt;

&lt;p&gt;If you're building AI agents, I'd recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use Mastra&lt;/strong&gt; if you want structure and Telex integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest time in good instructions&lt;/strong&gt; — they matter more than you think&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test locally first&lt;/strong&gt; — catch issues before deploying&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the logs&lt;/strong&gt; — they tell you everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build for flexibility&lt;/strong&gt; — real integrations are messier than documentation suggests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Daily Headline Digest Agent is live, integrated with Telex.im, and delivering real news to users. Not bad for a stage 3 backend challenge! 🚀&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; TypeScript, Mastra, Google Gemini 2.0 Flash, GNews API, Express, Railway&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployed:&lt;/strong&gt; November 7, 2025&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Ursulaonyi/news-digest-agent" rel="noopener noreferrer"&gt;https://github.com/Ursulaonyi/news-digest-agent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>telex</category>
      <category>mastra</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building a Production-Grade Automated Deployment Script</title>
      <dc:creator>Ursula Okafo</dc:creator>
      <pubDate>Mon, 20 Oct 2025 18:40:06 +0000</pubDate>
      <link>https://dev.to/ursulaonyi/building-a-production-grade-automated-deployment-script-3fgj</link>
      <guid>https://dev.to/ursulaonyi/building-a-production-grade-automated-deployment-script-3fgj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Automation is the backbone of modern DevOps. In this comprehensive guide, I'll walk you through building a production-grade Bash script that automates the entire deployment process for a Dockerized application on a remote Linux server. This is my journey through the HNG DevOps Stage 1 task.&lt;/p&gt;

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

&lt;p&gt;A single Bash script (&lt;code&gt;deploy.sh&lt;/code&gt;) that handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git repository authentication and cloning&lt;/li&gt;
&lt;li&gt;Remote server SSH connectivity&lt;/li&gt;
&lt;li&gt;Automated Docker, Docker Compose, and Nginx installation&lt;/li&gt;
&lt;li&gt;Docker image building and container deployment&lt;/li&gt;
&lt;li&gt;Nginx reverse proxy configuration&lt;/li&gt;
&lt;li&gt;Comprehensive logging and error handling&lt;/li&gt;
&lt;li&gt;Deployment validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Automation Matters
&lt;/h2&gt;

&lt;p&gt;Manual deployments are error-prone, time-consuming, and don't scale. By automating the entire process, we achieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Same process every time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Deploy in minutes, not hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: Fewer human errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeatability&lt;/strong&gt;: Deploy multiple times safely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: The script IS the deployment process&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│                    Local Machine                        │
│  ┌──────────────────────────────────────────────────┐  │
│  │  deploy.sh (Deployment Script)                   │  │
│  │  - Collects parameters                           │  │
│  │  - Clones Git repo                               │  │
│  │  - SSH into remote server                        │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
                          │
                          │ SSH
                          ▼
┌─────────────────────────────────────────────────────────┐
│              Remote Server (EC2/VPS)                    │
│  ┌──────────────────────────────────────────────────┐  │
│  │  Step 1: Install Docker &amp;amp; Nginx                  │  │
│  │  Step 2: Clone application repo                  │  │
│  │  Step 3: Build Docker image                      │  │
│  │  Step 4: Run container                           │  │
│  │  Step 5: Configure Nginx proxy                   │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The 8-Step Deployment Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Collect Parameters
&lt;/h3&gt;

&lt;p&gt;The script prompts for all necessary information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git repository URL&lt;/li&gt;
&lt;li&gt;Personal Access Token (for authentication)&lt;/li&gt;
&lt;li&gt;Git branch to deploy&lt;/li&gt;
&lt;li&gt;SSH credentials (username, IP, key path)&lt;/li&gt;
&lt;li&gt;Application port&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This interactive approach makes the script reusable and flexible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Enter Git Repository URL: https://github.com/yourname/your-app
Enter Personal Access Token &lt;span class="o"&gt;(&lt;/span&gt;PAT&lt;span class="o"&gt;)&lt;/span&gt;: ••••••••••
Enter Branch name &lt;span class="o"&gt;(&lt;/span&gt;default: main&lt;span class="o"&gt;)&lt;/span&gt;: main
Enter SSH Username: ubuntu
Enter Server IP Address: 54.123.45.67
Enter SSH Key Path: ~/.ssh/id_rsa
Enter Application Port: 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Clone the Repository
&lt;/h3&gt;

&lt;p&gt;The script authenticates using the PAT and clones the repository with the specified branch. This ensures we have the exact code version to deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Verify Docker Configuration
&lt;/h3&gt;

&lt;p&gt;Before proceeding, the script verifies that either a &lt;code&gt;Dockerfile&lt;/code&gt; or &lt;code&gt;docker-compose.yml&lt;/code&gt; exists. This validation prevents failed deployments early.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Test SSH Connection
&lt;/h3&gt;

&lt;p&gt;A connectivity check ensures the remote server is reachable before we start any remote operations. This saves time if there's a network issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Prepare Remote Environment
&lt;/h3&gt;

&lt;p&gt;The script SSH's into the server and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updates system packages&lt;/li&gt;
&lt;li&gt;Installs Docker and Docker Compose&lt;/li&gt;
&lt;li&gt;Installs Nginx&lt;/li&gt;
&lt;li&gt;Adds the user to the Docker group&lt;/li&gt;
&lt;li&gt;Enables and starts all services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All operations are idempotent—if Docker is already installed, the script skips it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Deploy the Application
&lt;/h3&gt;

&lt;p&gt;The script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transfers project files to the remote server&lt;/li&gt;
&lt;li&gt;Builds a Docker image&lt;/li&gt;
&lt;li&gt;Stops any old containers&lt;/li&gt;
&lt;li&gt;Launches a new container with the specified port&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 7: Configure Nginx
&lt;/h3&gt;

&lt;p&gt;A reverse proxy configuration is created that forwards HTTP traffic (port 80) to the Docker container's internal port. This allows external users to access your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8: Validate Deployment
&lt;/h3&gt;

&lt;p&gt;The script confirms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker service is running&lt;/li&gt;
&lt;li&gt;Container is active&lt;/li&gt;
&lt;li&gt;Nginx is running&lt;/li&gt;
&lt;li&gt;Application is accessible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Technical Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Bash Over Other Languages
&lt;/h3&gt;

&lt;p&gt;Why Bash instead of Python or Go?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Portability&lt;/strong&gt;: Works on any Linux/Unix system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dependencies&lt;/strong&gt;: Pre-installed on all servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: Close to shell commands we'd run manually&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-tested&lt;/strong&gt;: Used by major organizations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;The script uses &lt;code&gt;set -euo pipefail&lt;/code&gt; to catch errors immediately and &lt;code&gt;trap&lt;/code&gt; functions to handle unexpected failures gracefully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'handle_error $? $LINENO'&lt;/span&gt; ERR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logging
&lt;/h3&gt;

&lt;p&gt;All operations are logged to timestamped files:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This provides an audit trail and helps with debugging failed deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Idempotency
&lt;/h3&gt;

&lt;p&gt;The script can be run multiple times safely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It stops and removes old containers before creating new ones&lt;/li&gt;
&lt;li&gt;Installation commands check if packages exist first&lt;/li&gt;
&lt;li&gt;Nginx configuration is overwritten, not appended&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Repository Structure Matters
&lt;/h3&gt;

&lt;p&gt;Keep your deployment scripts alongside your application code in the same repository. This makes it easier for others to deploy your application without hunting for separate tools. A single repository with all necessary deployment files is more maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate Concerns
&lt;/h3&gt;

&lt;p&gt;While the deployment script lives in the app repository, you might keep a separate deployment folder locally for running deployments. This keeps your working directory organized without affecting the repository structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Configuration
&lt;/h3&gt;

&lt;p&gt;Store &lt;code&gt;.env&lt;/code&gt; files in your repository for non-sensitive defaults, but add them to &lt;code&gt;.gitignore&lt;/code&gt; for production deployments. For sensitive data (API keys, database passwords), use GitHub Secrets or environment variable management tools instead of checking them into version control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Before Deployment
&lt;/h3&gt;

&lt;p&gt;Always test your endpoints after deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://your-server-ip/
curl http://your-server-ip/health
curl http://your-server-ip/api/info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SSH Key Management
&lt;/h3&gt;

&lt;p&gt;Store SSH keys securely and never commit them to repositories. Use appropriate file permissions (&lt;code&gt;chmod 600&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What We Didn't Include (But Should in Production)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSL/TLS Certificates&lt;/strong&gt;: Use Let's Encrypt with Certbot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Checks&lt;/strong&gt;: Kubernetes-style readiness probes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt;: Prometheus, Datadog, or similar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt;: Load balancers for multiple instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup Strategy&lt;/strong&gt;: Database backups before deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback Plan&lt;/strong&gt;: Ability to revert to previous versions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use GitHub Secrets instead of manual PAT entry&lt;/li&gt;
&lt;li&gt;Implement IP whitelisting for SSH access&lt;/li&gt;
&lt;li&gt;Use IAM roles instead of long-lived credentials&lt;/li&gt;
&lt;li&gt;Scan Docker images for vulnerabilities&lt;/li&gt;
&lt;li&gt;Implement least-privilege Docker containers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CI/CD Integration
&lt;/h3&gt;

&lt;p&gt;This script integrates well with GitHub Actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;chmod +x deploy.sh&lt;/span&gt;
    &lt;span class="s"&gt;./deploy.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;After running the deployment script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application successfully deployed to EC2&lt;/li&gt;
&lt;li&gt;Accessible at &lt;code&gt;http://3.78.183.186&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;All endpoints responding correctly&lt;/li&gt;
&lt;li&gt;Nginx proxying traffic to container&lt;/li&gt;
&lt;li&gt;Logs show successful deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deploy Wizard - HNG DevOps Stage 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-20T17:26:58.469Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Building an automated deployment script teaches you the fundamentals of DevOps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as code&lt;/li&gt;
&lt;li&gt;Automation principles&lt;/li&gt;
&lt;li&gt;Server configuration&lt;/li&gt;
&lt;li&gt;Container orchestration basics&lt;/li&gt;
&lt;li&gt;Monitoring and validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This script is a foundation you can build upon. In a real-world scenario, you'd integrate it with CI/CD pipelines, add monitoring, implement rollback strategies, and scale to multiple servers.&lt;/p&gt;

&lt;p&gt;The journey from manual deployments to automation is what separates junior developers from seasoned DevOps engineers. Start here, and keep building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;The complete deployment script uses several key patterns that make it production-ready:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: The script implements bash error handling with &lt;code&gt;set -euo pipefail&lt;/code&gt; and trap functions to catch failures and provide meaningful error messages at each stage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging&lt;/strong&gt;: All operations are logged to timestamped files, creating an audit trail for debugging and compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;: The script safely handles re-runs by checking if services exist before installation and cleanly stopping containers before redeployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH Integration&lt;/strong&gt;: Rather than requiring multiple manual SSH commands, the script orchestrates the entire remote deployment through a single SSH connection, executing setup and deployment steps sequentially.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameter Validation&lt;/strong&gt;: User inputs are validated immediately (checking SSH keys exist, ports are numbers, URLs have correct format) to fail fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  View the Full Code
&lt;/h3&gt;

&lt;p&gt;The complete deployment script and all supporting files are available in the repository:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Ursulaonyi/deploy-wizard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the repository you'll find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;deploy.sh&lt;/code&gt; - The main deployment script&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt; - Container configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt; - Node.js dependencies&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.js&lt;/code&gt; - Sample application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;README.md&lt;/code&gt; - Detailed documentation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt; - Version control configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to fork, modify, and use this script as a foundation for your own deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/bash/manual/" rel="noopener noreferrer"&gt;Bash Scripting Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/" rel="noopener noreferrer"&gt;Docker Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/" rel="noopener noreferrer"&gt;Nginx Reverse Proxy Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linux-audit.com/ssh-hardening-best-practices/" rel="noopener noreferrer"&gt;SSH Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Have you built your own deployment script? Share your experience in the comments below.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>bash</category>
    </item>
    <item>
      <title>Building a Production-Ready String Analyzer API with Node.js and EC2</title>
      <dc:creator>Ursula Okafo</dc:creator>
      <pubDate>Mon, 20 Oct 2025 11:32:40 +0000</pubDate>
      <link>https://dev.to/ursulaonyi/building-a-production-ready-string-analyzer-api-with-nodejs-and-ec2-5dmk</link>
      <guid>https://dev.to/ursulaonyi/building-a-production-ready-string-analyzer-api-with-nodejs-and-ec2-5dmk</guid>
      <description>&lt;p&gt;&lt;em&gt;A complete guide to creating, deploying, and scaling a RESTful API service&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Ever wondered what makes a string special? Is it a palindrome? How many unique characters does it have? What's its cryptographic fingerprint? &lt;/p&gt;

&lt;p&gt;I recently built a String Analyzer API that answers all these questions and more. In this post, I'll walk you through the entire journey—from the first line of code to a production deployment on AWS EC2 with Nginx as a reverse proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does It Do?
&lt;/h2&gt;

&lt;p&gt;The String Analyzer API is a RESTful service that accepts any string and returns comprehensive analysis including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Length and word count&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Palindrome detection&lt;/strong&gt; (case-insensitive)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unique character count&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Character frequency mapping&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA256 hash&lt;/strong&gt; for unique identification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced filtering&lt;/strong&gt; with multiple criteria&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Natural language queries&lt;/strong&gt; ("show me single word palindromes")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what happens when you analyze the string "ursula is a devops engineer":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8f3e825d9f5b2c7a4e1f6d9c3b8a5e2f..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ursula is a devops engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"is_palindrome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unique_characters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sha256_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8f3e825d9f5b2c7a4e1f6d9c3b8a5e2f..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"character_frequency_map"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"u"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"l"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"p"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"g"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-20T12:00:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;I chose a modern, lightweight stack that prioritizes performance and simplicity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js + Express&lt;/strong&gt;: Fast, minimal web framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crypto module&lt;/strong&gt;: Built-in SHA256 hashing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-memory storage&lt;/strong&gt;: Lightning-fast data access (perfect for MVP)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PM2&lt;/strong&gt;: Process management for production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt;: Reverse proxy for clean URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS EC2&lt;/strong&gt;: Free tier Ubuntu instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No databases, no heavy ORMs—just pure JavaScript efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. In-Memory Storage
&lt;/h3&gt;

&lt;p&gt;Instead of reaching for MongoDB or PostgreSQL, I implemented a simple Map-based storage system. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Microsecond access times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: No schema migrations or connection pools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for MVP&lt;/strong&gt;: Easy to swap later if needed
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;stringStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stringStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stringStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. SHA256 as Primary Key
&lt;/h3&gt;

&lt;p&gt;Each string gets a unique identifier based on its SHA256 hash. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No duplicate strings (hash collision = duplicate detected)&lt;/li&gt;
&lt;li&gt;Content-addressable storage&lt;/li&gt;
&lt;li&gt;Cryptographically secure IDs&lt;/li&gt;
&lt;li&gt;Same string always gets same ID&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. RESTful Design
&lt;/h3&gt;

&lt;p&gt;The API follows REST principles strictly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /strings&lt;/code&gt; - Create&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /strings/:stringValue&lt;/code&gt; - Read one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /strings&lt;/code&gt; - Read all (with filters)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /strings/:stringValue&lt;/code&gt; - Delete&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /strings/filter-by-natural-language&lt;/code&gt; - Natural language queries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Natural Language Processing
&lt;/h3&gt;

&lt;p&gt;The coolest feature? You can query in plain English:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /strings/filter-by-natural-language?query&lt;span class="o"&gt;=&lt;/span&gt;single word palindromic strings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API parses your intent and translates it to filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parsed_filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"is_palindrome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  String Analysis Algorithm
&lt;/h3&gt;

&lt;p&gt;The core analysis function is elegantly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;analyzeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Length&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Palindrome check (case-insensitive, ignore spaces)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleanString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;is_palindrome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cleanString&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;cleanString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Unique characters&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unique_characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Word count&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// SHA256 hash&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sha256_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Character frequency&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;character_frequency_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;character_frequency_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;character_frequency_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;is_palindrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unique_characters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sha256_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;character_frequency_map&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Filtering
&lt;/h3&gt;

&lt;p&gt;Users can combine multiple filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /strings?is_palindrome&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&amp;amp;min_length&lt;span class="o"&gt;=&lt;/span&gt;5&amp;amp;word_count&lt;span class="o"&gt;=&lt;/span&gt;1&amp;amp;contains_character&lt;span class="o"&gt;=&lt;/span&gt;a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller chains filters efficiently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_palindrome&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_palindrome&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_palindrome&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min_length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min_length&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... more filters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;Proper HTTP status codes make the API predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;201 Created&lt;/code&gt; - String successfully analyzed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400 Bad Request&lt;/code&gt; - Missing or invalid parameters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;404 Not Found&lt;/code&gt; - String doesn't exist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;409 Conflict&lt;/code&gt; - String already exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;422 Unprocessable Entity&lt;/code&gt; - Wrong data type&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;500 Internal Server Error&lt;/code&gt; - Something went wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Local Development
&lt;/h3&gt;

&lt;p&gt;Started with a clean Node.js setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;string-analyzer-api
&lt;span class="nb"&gt;cd &lt;/span&gt;string-analyzer-api
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express body-parser cors dotenv crypto
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Organized the code into a clean structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── controllers/    # Request handlers
├── routes/         # API endpoints
├── storage/        # Data persistence
├── utils/          # Business logic
└── server.js       # Entry point
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: AWS EC2 Setup
&lt;/h3&gt;

&lt;p&gt;Launched a t2.micro Ubuntu instance (free tier):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Group Configuration&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port 22 (SSH) - for admin access&lt;/li&gt;
&lt;li&gt;Port 80 (HTTP) - for public API access&lt;/li&gt;
&lt;li&gt;Port 3000 (optional) - for direct Node.js access&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System Preparation&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_18.x | &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs git nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Process Management with PM2
&lt;/h3&gt;

&lt;p&gt;PM2 keeps the application running 24/7:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pm2
pm2 start src/server.js &lt;span class="nt"&gt;--name&lt;/span&gt; string-analyzer
pm2 startup  &lt;span class="c"&gt;# Auto-start on reboot&lt;/span&gt;
pm2 save     &lt;span class="c"&gt;# Save current process list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits of PM2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-restart&lt;/strong&gt; on crashes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; (can run multiple instances)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Log management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring dashboard&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Nginx Reverse Proxy
&lt;/h3&gt;

&lt;p&gt;Instead of exposing port 3000, I configured Nginx to forward traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean URLs&lt;/strong&gt; (no :3000 suffix)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL termination&lt;/strong&gt; capability (add Let's Encrypt later)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; (scale horizontally if needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static file serving&lt;/strong&gt; (future expansion)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing It Out
&lt;/h2&gt;

&lt;p&gt;Let's create and query some strings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a string:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://your-api.com/strings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"value":"ursula is a devops engineer"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query with filters:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://your-api.com/strings?word_count=5&amp;amp;min_length=20"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Natural language search:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://your-api.com/strings/filter-by-natural-language?query=strings longer than 25 characters"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Retrieve specific string:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://your-api.com/strings/ursula%20is%20a%20devops%20engineer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  In-Memory Trade-offs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sub-millisecond response times&lt;/li&gt;
&lt;li&gt;No network latency&lt;/li&gt;
&lt;li&gt;Simple implementation&lt;/li&gt;
&lt;li&gt;Perfect for caching layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data lost on restart&lt;/li&gt;
&lt;li&gt;Limited by RAM&lt;/li&gt;
&lt;li&gt;Single-server limitation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Solution for Production:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add Redis for persistent caching&lt;/li&gt;
&lt;li&gt;Use PostgreSQL for long-term storage&lt;/li&gt;
&lt;li&gt;Implement write-through cache pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scalability Path
&lt;/h3&gt;

&lt;p&gt;Current setup handles ~1000 requests/second on t2.micro. To scale:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vertical&lt;/strong&gt;: Upgrade to larger EC2 instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal&lt;/strong&gt;: Add load balancer + multiple instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: Add CloudFront CDN for GET requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Migrate to RDS for persistence&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start Simple
&lt;/h3&gt;

&lt;p&gt;I could have added a database from day one, but in-memory storage taught me to focus on core features first. Premature optimization is real.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. REST Principles Matter
&lt;/h3&gt;

&lt;p&gt;Following RESTful conventions made the API intuitive. Developers know exactly what &lt;code&gt;POST /strings&lt;/code&gt; does.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Error Messages Save Time
&lt;/h3&gt;

&lt;p&gt;Clear, specific error messages saved hours of debugging. "Missing 'value' field" beats "Bad Request" every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Natural Language is Hard
&lt;/h3&gt;

&lt;p&gt;Building even a simple NLP parser was challenging. Regex patterns only go so far—production systems need better approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. DevOps is Part of Development
&lt;/h3&gt;

&lt;p&gt;Setting up PM2 and Nginx taught me that deployment is as important as code quality. An app that crashes in production is a failed app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;Here's what's on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent Storage&lt;/strong&gt;: Add PostgreSQL with automatic migration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: JWT-based API keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting&lt;/strong&gt;: Prevent abuse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks&lt;/strong&gt;: Notify on new strings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced NLP&lt;/strong&gt;: Better natural language understanding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch Processing&lt;/strong&gt;: Analyze multiple strings at once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String Comparison&lt;/strong&gt;: Find similar strings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export Formats&lt;/strong&gt;: CSV, JSON, XML downloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics Dashboard&lt;/strong&gt;: Visualize string patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The entire project is open source and deployment-ready:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live API&lt;/strong&gt;: Check out the working demo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repo&lt;/strong&gt;: Clone and deploy in minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Documentation&lt;/strong&gt;: Complete API reference included&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're learning Node.js, practicing DevOps, or building a production service, this project covers the full stack from code to cloud.&lt;/p&gt;

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

&lt;p&gt;Building this String Analyzer API was a fantastic journey through modern web development. From Express routing to AWS deployment, every step taught valuable lessons about creating production-ready services.&lt;/p&gt;

&lt;p&gt;The best part? It took less than a day from first commit to live deployment. That's the power of choosing the right tools and keeping things simple.&lt;/p&gt;

&lt;p&gt;What would you analyze with this API? What features would you add? Drop your thoughts in the comments!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Node.js · Express · AWS EC2 · Nginx · PM2&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Deployment Time&lt;/strong&gt;: ~2 hours&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Lines of Code&lt;/strong&gt;: ~300&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to build something similar? Check out the full tutorial and source code on &lt;a href="https://github.com/Ursulaonyi/string-analyzer-api" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Stars and contributions welcome!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>aws</category>
      <category>tutorial</category>
      <category>node</category>
    </item>
    <item>
      <title>Building and Deploying a Django REST API on AWS EC2: A Complete Guide</title>
      <dc:creator>Ursula Okafo</dc:creator>
      <pubDate>Sun, 19 Oct 2025 08:41:25 +0000</pubDate>
      <link>https://dev.to/ursulaonyi/building-and-deploying-a-django-rest-api-on-aws-ec2-a-complete-guide-2ba4</link>
      <guid>https://dev.to/ursulaonyi/building-and-deploying-a-django-rest-api-on-aws-ec2-a-complete-guide-2ba4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this article, I'll walk you through my journey of building a dynamic REST API with Django and deploying it to AWS EC2. This project is part of the HNG 13 Backend Wizards track, and it's a fantastic way to learn API development, third-party integrations, and cloud deployment.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you'll have a live API endpoint serving real data from your Django application on AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we're building:&lt;/strong&gt; A &lt;code&gt;/me&lt;/code&gt; endpoint that returns your profile information along with random cat facts from an external API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Project Overview&lt;/li&gt;
&lt;li&gt;Local Development Setup&lt;/li&gt;
&lt;li&gt;Understanding the Code&lt;/li&gt;
&lt;li&gt;Deploying to AWS EC2&lt;/li&gt;
&lt;li&gt;Testing and Verification&lt;/li&gt;
&lt;li&gt;Key Learnings&lt;/li&gt;
&lt;li&gt;Troubleshooting Tips&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Project Overview {#project-overview}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What We're Building
&lt;/h3&gt;

&lt;p&gt;A simple yet powerful REST API endpoint with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User profile information (email, name, tech stack)&lt;/li&gt;
&lt;li&gt;Dynamic timestamps in ISO 8601 format&lt;/li&gt;
&lt;li&gt;Integration with an external Cat Facts API&lt;/li&gt;
&lt;li&gt;Graceful error handling with fallbacks&lt;/li&gt;
&lt;li&gt;Production-ready deployment on AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Django + Django REST Framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; Gunicorn (WSGI application server)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse Proxy:&lt;/strong&gt; Nginx&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; AWS EC2 (Ubuntu 22.04 LTS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Python 3.10+&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why This Stack?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django:&lt;/strong&gt; Robust, batteries-included framework with excellent documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DRF:&lt;/strong&gt; Simple and powerful for building REST APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gunicorn:&lt;/strong&gt; Reliable WSGI server for production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx:&lt;/strong&gt; High-performance reverse proxy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS:&lt;/strong&gt; Industry-standard cloud platform with free tier&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Local Development Setup {#local-development}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create Your Project Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;backend-wizards-stage0
&lt;span class="nb"&gt;cd &lt;/span&gt;backend-wizards-stage0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Set Up Virtual Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows: venv\Scripts\activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always use virtual environments! They isolate project dependencies and prevent conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Install Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;django djangorestframework python-dotenv requests
pip freeze &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why each package?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;django&lt;/code&gt; - Web framework&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;djangorestframework&lt;/code&gt; - Simplified API building&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python-dotenv&lt;/code&gt; - Load environment variables from &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requests&lt;/code&gt; - HTTP library for consuming external APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Initialize Django Project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;django-admin startproject config &lt;span class="nb"&gt;.&lt;/span&gt;
python manage.py startapp api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Naming convention:&lt;/strong&gt; I use &lt;code&gt;config&lt;/code&gt; for the project folder to avoid naming conflicts with the &lt;code&gt;django&lt;/code&gt; library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Configure Settings
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;config/settings.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Your environment variables
&lt;/span&gt;&lt;span class="n"&gt;EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EMAIL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;BACKEND_STACK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BACKEND_STACK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CAT_API_TIMEOUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CAT_API_TIMEOUT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rest_framework&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Add this
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;# Add this
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Create &lt;code&gt;.env&lt;/code&gt; File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EMAIL=your-email@example.com
NAME=Your Full Name
BACKEND_STACK=Python/Django
CAT_API_TIMEOUT=5
DEBUG=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Never commit &lt;code&gt;.env&lt;/code&gt; to version control. Add it to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Code {#code-walkthrough}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Main API View
&lt;/h3&gt;

&lt;p&gt;Here's the heart of the project (&lt;code&gt;api/views.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.decorators&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;api_view&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@api_view&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;profile_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Returns user profile with a dynamic cat fact
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Fetch cat fact from external API
&lt;/span&gt;        &lt;span class="n"&gt;cat_fact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_cat_fact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Build response
&lt;/span&gt;        &lt;span class="n"&gt;response_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BACKEND_STACK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fact&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cat_fact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_200_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error in profile endpoint: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to fetch profile data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_500_INTERNAL_SERVER_ERROR&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_cat_fact&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Fetches a random cat fact from the Cat Facts API
    Returns a fallback fact if the API fails
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://catfact.ninja/fact&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CAT_API_TIMEOUT&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fact&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cat Facts API timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cats have over 20 different vocal sounds!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to fetch cat fact: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cats spend 70% of their lives sleeping!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unexpected error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A cat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s purr may promote bone healing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Concepts Here
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Decorators:&lt;/strong&gt; &lt;code&gt;@api_view(['GET'])&lt;/code&gt; - Restricts the endpoint to GET requests only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Dynamic Timestamps:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the timestamp is always current and in ISO 8601 format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Error Handling:&lt;/strong&gt; Multiple except blocks handle different failure scenarios gracefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Timeouts:&lt;/strong&gt; &lt;code&gt;timeout=settings.CAT_API_TIMEOUT&lt;/code&gt; prevents hanging indefinitely if the external API is slow.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Configuration
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;api/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then include it in &lt;code&gt;config/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api.urls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit: &lt;code&gt;http://127.0.0.1:8000/me&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Expected response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Full Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Python/Django"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-19T14:35:22.123Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cats have a specialized collarbone..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Deploying to AWS EC2 {#aws-deployment}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why AWS EC2?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Free tier available (t2.micro for 12 months)&lt;/li&gt;
&lt;li&gt;Full control over your server&lt;/li&gt;
&lt;li&gt;Scalable infrastructure&lt;/li&gt;
&lt;li&gt;Industry-standard platform&lt;/li&gt;
&lt;li&gt;Perfect learning tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Launch an EC2 Instance
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign in to AWS Console&lt;/li&gt;
&lt;li&gt;Navigate to EC2 Dashboard&lt;/li&gt;
&lt;li&gt;Click "Launch Instance"&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Ubuntu 22.04 LTS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Instance type: &lt;strong&gt;t2.micro&lt;/strong&gt; (free tier)&lt;/li&gt;
&lt;li&gt;Storage: 30GB (default)&lt;/li&gt;
&lt;li&gt;Security Group:

&lt;ul&gt;
&lt;li&gt;SSH (22) - from your IP&lt;/li&gt;
&lt;li&gt;HTTP (80) - from anywhere&lt;/li&gt;
&lt;li&gt;HTTPS (443) - from anywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create a key pair (&lt;code&gt;.pem&lt;/code&gt; file) and download it&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Connect to Your Instance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;400 your-key.pem
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-key.pem ubuntu@your-ec2-public-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;your-ec2-public-ip&lt;/code&gt; with your instance's public IP from the AWS console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Install System Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; python3-pip python3-venv git nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Clone Your Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /home/ubuntu
git clone https://github.com/YOUR_USERNAME/backend-wizards-stage0.git
&lt;span class="nb"&gt;cd &lt;/span&gt;backend-wizards-stage0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Set Up Python Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
pip &lt;span class="nb"&gt;install &lt;/span&gt;gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Configure Environment Variables
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Add your variables and save (Ctrl+X, Y, Enter).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Update Django Settings
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;config/settings.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED_HOSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-ec2-public-ip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Never leave DEBUG=True in production!&lt;/strong&gt; It exposes sensitive information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Create Systemd Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/systemd/system/django.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Django Gunicorn application&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ubuntu&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/ubuntu/backend-wizards-stage0&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/ubuntu/backend-wizards-stage0/venv/bin/gunicorn config.wsgi:application --bind 0.0.0.0:8000&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures Django restarts automatically if it crashes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Enable and Start Django
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;django.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start django.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status django.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 10: Configure Nginx
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="s"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt; &lt;span class="s"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 11: Start Nginx
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 12: Test Your API
&lt;/h3&gt;

&lt;p&gt;Visit: &lt;code&gt;http://your-ec2-public-ip/me&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🎉 Your API is now live!&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing and Verification {#testing}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual Testing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://your-ec2-public-ip/me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Response Headers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://your-ec2-public-ip/me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Content-Type: application/json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verify Dynamic Updates
&lt;/h3&gt;

&lt;p&gt;Call the endpoint multiple times and check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Timestamp changes each time&lt;/li&gt;
&lt;li&gt;✅ Cat fact is different each time&lt;/li&gt;
&lt;li&gt;✅ Response is consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test Error Handling
&lt;/h3&gt;

&lt;p&gt;Check logs to ensure errors are logged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; django.service &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Learnings {#learnings}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;API Design Matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Consistent response structures make client integration easier. Always version your APIs and document endpoints clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Error Handling is Critical&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Never let your API fail silently. Provide fallback responses and log everything. Users appreciate transparent error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Environment Variables are Essential&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Never hardcode credentials or configuration. Use &lt;code&gt;.env&lt;/code&gt; files and keep them secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Testing is Non-Negotiable&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Test locally before deploying. Test from different networks. Test error scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Monitoring and Logging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Always log important events. Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warning message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Info message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;strong&gt;Timeouts Prevent Hanging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Always set timeouts on external API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. &lt;strong&gt;Reverse Proxies Improve Performance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Nginx sits between users and Gunicorn, handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSL termination&lt;/li&gt;
&lt;li&gt;Load balancing&lt;/li&gt;
&lt;li&gt;Compression&lt;/li&gt;
&lt;li&gt;Static file serving&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. &lt;strong&gt;Systemd Services Ensure Availability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using systemd services with &lt;code&gt;Restart=always&lt;/code&gt; ensures your API stays up even after crashes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting Tips {#troubleshooting}
&lt;/h2&gt;

&lt;h3&gt;
  
  
  502 Bad Gateway
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Nginx can't reach Gunicorn&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status django.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; django.service &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure Gunicorn is running on port 8000.&lt;/p&gt;

&lt;h3&gt;
  
  
  404 Not Found
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Endpoint returns 404&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify URL: &lt;code&gt;http://ip/me&lt;/code&gt; (not &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;/api/me&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;urls.py&lt;/code&gt; routing&lt;/li&gt;
&lt;li&gt;Ensure &lt;code&gt;api&lt;/code&gt; is in &lt;code&gt;INSTALLED_APPS&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Environment Variables Not Loading
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Settings are None&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify &lt;code&gt;.env&lt;/code&gt; file exists in project root&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;.env&lt;/code&gt; file syntax&lt;/li&gt;
&lt;li&gt;Restart Django: &lt;code&gt;sudo systemctl restart django.service&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cat Facts API Timeout
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; API returns fallback facts consistently&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check internet connection&lt;/li&gt;
&lt;li&gt;Verify firewall isn't blocking outbound requests&lt;/li&gt;
&lt;li&gt;Increase timeout in &lt;code&gt;.env&lt;/code&gt;: &lt;code&gt;CAT_API_TIMEOUT=10&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Building and deploying a Django API on AWS taught me valuable lessons about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST API design principles&lt;/li&gt;
&lt;li&gt;Error handling and resilience&lt;/li&gt;
&lt;li&gt;Production deployment strategies&lt;/li&gt;
&lt;li&gt;Linux server management&lt;/li&gt;
&lt;li&gt;Monitoring and logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project serves as a solid foundation for more complex applications. From here, you can add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database models&lt;/li&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Advanced caching&lt;/li&gt;
&lt;li&gt;Horizontal scaling&lt;/li&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a database (PostgreSQL)&lt;/li&gt;
&lt;li&gt;Implement authentication (JWT)&lt;/li&gt;
&lt;li&gt;Write automated tests&lt;/li&gt;
&lt;li&gt;Set up CI/CD with GitHub Actions&lt;/li&gt;
&lt;li&gt;Add API documentation (Swagger)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/" rel="noopener noreferrer"&gt;Django Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.django-rest-framework.org/" rel="noopener noreferrer"&gt;Django REST Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;AWS EC2 Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/" rel="noopener noreferrer"&gt;Nginx Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gunicorn.org/" rel="noopener noreferrer"&gt;Gunicorn Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Let's Connect
&lt;/h2&gt;

&lt;p&gt;If you found this helpful, feel free to reach out! I'm always excited to discuss backend development, cloud deployment, and web technologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; [Your GitHub Profile]&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Twitter/X:&lt;/strong&gt; [@Your Handle]&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:ursulaokafo32@gmail.com"&gt;ursulaokafo32@gmail.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is part of my journey through the HNG 13 Backend Wizards track. Happy coding! 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>aws</category>
      <category>tutorial</category>
      <category>django</category>
    </item>
    <item>
      <title>Setting Up a Simple Web Server on a Fresh Ubuntu Server with NGINX</title>
      <dc:creator>Ursula Okafo</dc:creator>
      <pubDate>Wed, 29 Jan 2025 10:09:09 +0000</pubDate>
      <link>https://dev.to/ursulaonyi/deploying-an-nginx-web-server-on-aws-ec2-my-devops-stage-0-experience-mjp</link>
      <guid>https://dev.to/ursulaonyi/deploying-an-nginx-web-server-on-aws-ec2-my-devops-stage-0-experience-mjp</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;During the &lt;strong&gt;DevOps Stage 0 in the HNG Internship&lt;/strong&gt;, I was tasked with setting up a simple web server using NGINX on an Ubuntu server. This task aimed to test my ability to work with basic web server configurations while ensuring my deployed server was publicly accessible. It sounded straightforward at first, but this task ended up teaching me quite a lot about web servers, Linux, and cloud platforms. Here's a breakdown of my experience, what I learned, and how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Chose Azure:
&lt;/h2&gt;

&lt;p&gt;For this task, I used an Ubuntu server hosted on Microsoft Azure. The main reason? I’m a new Azure user and received free credits as part of their welcome package. This made Azure a budget-friendly and convenient choice for launching a virtual machine without spending a dime.&lt;br&gt;
Beyond the free credits, Azure provides a clean and beginner-friendly interface, which made it easy to deploy and manage my VM. I was able to get the server up and running quickly, allowing me to focus entirely on learning and configuring NGINX.&lt;br&gt;
&lt;strong&gt;Note: You’re not limited to Azure!&lt;/strong&gt;&lt;br&gt;
This task can be completed using any cloud platform like AWS, Google Cloud Platform (GCP), or even a local virtual machine. The important part is understanding how to work with the server, not where it's hosted.&lt;/p&gt;
&lt;h2&gt;
  
  
  🔧 What Is NGINX? (Like You’ve Never Heard of It)
&lt;/h2&gt;

&lt;p&gt;If you’re like me before this task, you might be wondering: “What in the world is NGINX?”&lt;br&gt;
Well, NGINX (pronounced "Engine-X") is a powerful and super popular web server. You can think of it like a smart waiter in a restaurant. When someone visits your website, NGINX takes that request, finds the correct page or file, and brings it back to their browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But NGINX doesn’t just stop at being a “waiter.” It’s also a:&lt;/strong&gt;&lt;br&gt;
🖥️ Web server&lt;br&gt;
🔁 Reverse proxy (passes requests to another server)&lt;br&gt;
⚖️ Load balancer (shares traffic across multiple servers)&lt;br&gt;
🔐 SSL handler (helps secure your website)&lt;br&gt;
📦 Cache (speeds things up by storing responses)&lt;br&gt;
It’s lightweight, fast, and used by companies like Netflix, Dropbox, and WordPress. Pretty cool for something I hadn’t heard of before this task!&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started with Azure Virtual Machine(VM)
&lt;/h2&gt;

&lt;p&gt;Setting up a VM on Microsoft Azure was a smooth process. Here’s what I selected during setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chose the Ubuntu 22.04 LTS image from the Azure Marketplace.&lt;/li&gt;
&lt;li&gt;Selected the Standard B1s size (which is eligible for Azure Free Tier).&lt;/li&gt;
&lt;li&gt;Created a new resource group to organize my resources.&lt;/li&gt;
&lt;li&gt;Allowed SSH (port 22) and HTTP (port 80) through the network security group (NSG) settings to enable secure access and web traffic.&lt;/li&gt;
&lt;li&gt;Generated a new SSH key pair to securely connect to the VM from my terminal using the .pem file (or convert to .ppk for PuTTY on Windows).&lt;/li&gt;
&lt;li&gt;Chose a region closest to me for better latency.&lt;/li&gt;
&lt;li&gt;Launched the VM and connected to it via SSH to begin configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🛠️ Setting Up NGINX: Steps I Followed
&lt;/h2&gt;

&lt;p&gt;Here’s a summary of how I installed and configured NGINX:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update Server Packages:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Install NGINX:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Start and Enable NGINX:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

# Check if Nginx is running
sudo systemctl status nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Test the Setup&lt;/strong&gt;&lt;br&gt;
I accessed my public IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your-public-ip&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and confirmed that nginx is rightly configured.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp8zeecusekd7nucmkj8.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp8zeecusekd7nucmkj8.JPG" alt="Image description" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a Custom HTML Page:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Welcome to DevOps Stage 0 - [Your Name]/[SlackName], we are glad to have you" | sudo tee /var/www/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwu9s9u3rsz2w8civlj0.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwu9s9u3rsz2w8civlj0.JPG" alt="Image description" width="800" height="54"&gt;&lt;/a&gt;&lt;br&gt;
NB: (I replaced [Your Name] and [SlackName] with my actual name and Slack username.)&lt;br&gt;
Access your IP address once more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your-public-ip&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And boom — my custom message was live on the web!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;This task taught me several important lessons:&lt;br&gt;&lt;br&gt;
✅ How to launch an instance and configure it for web hosting.&lt;br&gt;&lt;br&gt;
✅ Setting up a basic web server using NGINX.&lt;br&gt;&lt;br&gt;
✅ Managing cloud security settings to allow public access.&lt;br&gt;&lt;br&gt;
✅ Troubleshooting connectivity issues related to firewall and inbound rules.  &lt;/p&gt;

&lt;h2&gt;
  
  
  😅 Challenges I Faced
&lt;/h2&gt;

&lt;p&gt;Permission issues: When trying to write to /var/www/html/index.html, I got denied until I added sudo.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 What I Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This task taught me a lot more than just typing commands:&lt;/li&gt;
&lt;li&gt;I now understand what a web server does and how to use one.&lt;/li&gt;
&lt;li&gt;I learned how to use Azure’s cloud platform to host my own VM.&lt;/li&gt;
&lt;li&gt;I got hands-on with Linux and command-line tools, which are key skills in DevOps.&lt;/li&gt;
&lt;li&gt;It was also a fun reminder of how small configurations can power big things on the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/devops-engineers" rel="noopener noreferrer"&gt;DevOps Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/cloud-engineers" rel="noopener noreferrer"&gt;Cloud Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/site-reliability-engineers" rel="noopener noreferrer"&gt;Site Reliability Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/platform-engineers" rel="noopener noreferrer"&gt;Platform Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/infrastructure-engineers" rel="noopener noreferrer"&gt;Infrastructure Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/kubernetes-specialists" rel="noopener noreferrer"&gt;Kubernetes Specialists&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/aws-solutions-architects" rel="noopener noreferrer"&gt;AWS Solutions Architects&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/azure-devops-engineers" rel="noopener noreferrer"&gt;Azure DevOps Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/google-cloud-engineers" rel="noopener noreferrer"&gt;Google Cloud Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/ci-cd-pipeline-engineers" rel="noopener noreferrer"&gt;CI/CD Pipeline Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/monitoring-observability-engineers" rel="noopener noreferrer"&gt;Monitoring/Observability Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/automation-engineers" rel="noopener noreferrer"&gt;Automation Engineers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/docker-specialists" rel="noopener noreferrer"&gt;Docker Specialists&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/linux-developers" rel="noopener noreferrer"&gt;Linux Developers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hng.tech/hire/postgresql-developers" rel="noopener noreferrer"&gt;PostgreSQL Developers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>hng</category>
      <category>beginners</category>
      <category>cloudcomputing</category>
    </item>
  </channel>
</rss>
