<?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: Emidowojo</title>
    <description>The latest articles on DEV Community by Emidowojo (@techondiapers).</description>
    <link>https://dev.to/techondiapers</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F375779%2Fefc4f692-d075-47c7-8fc8-727fa1c61a0a.JPG</url>
      <title>DEV Community: Emidowojo</title>
      <link>https://dev.to/techondiapers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/techondiapers"/>
    <language>en</language>
    <item>
      <title>Building a Reliable Payment Tracker with Daya's API and AWS</title>
      <dc:creator>Emidowojo</dc:creator>
      <pubDate>Sun, 21 Jun 2026 21:49:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-reliable-payment-tracker-with-dayas-api-and-aws-3hpe</link>
      <guid>https://dev.to/aws-builders/building-a-reliable-payment-tracker-with-dayas-api-and-aws-3hpe</guid>
      <description>&lt;p&gt;One thing I have learned from working around payment systems is that collecting money is usually the easy part.&lt;/p&gt;

&lt;p&gt;The harder problem is knowing, with confidence, that the money arrived and that your system accounted for it correctly.&lt;/p&gt;

&lt;p&gt;At first glance, the flow seems straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate payment details.&lt;/li&gt;
&lt;li&gt;Give those details to a customer.&lt;/li&gt;
&lt;li&gt;Wait for payment.&lt;/li&gt;
&lt;li&gt;Update your records.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In reality, payment systems operate in a world of retries, network failures, duplicate events, and downstream services that occasionally decide today is not their day.&lt;/p&gt;

&lt;p&gt;That was the problem I wanted to explore when I built this payment tracker.&lt;/p&gt;

&lt;p&gt;Instead of focusing only on payment collection, I wanted to build something that could safely handle the full lifecycle of a payment event, from payment creation to reconciliation, while remaining reliable when parts of the system fail.&lt;/p&gt;

&lt;p&gt;For payment collection, I used &lt;a href="https://daya.co/" rel="noopener noreferrer"&gt;Daya&lt;/a&gt;. For reliability and operational infrastructure, I used AWS.&lt;/p&gt;

&lt;p&gt;Daya provides the payment infrastructure: funding accounts, bank-transfer collection, crypto-address collection, and deposit webhook events. AWS provides the backbone that lets the application process those events reliably: webhook endpoints, queues, workers, durable storage, secrets management, monitoring, and alerts.&lt;/p&gt;

&lt;p&gt;The result is a simple payment tracker that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create NGN bank-transfer payment details.&lt;/li&gt;
&lt;li&gt;Create crypto deposit addresses.&lt;/li&gt;
&lt;li&gt;Receive and verify signed Daya webhooks.&lt;/li&gt;
&lt;li&gt;Queue payment events before processing them.&lt;/li&gt;
&lt;li&gt;Process Daya deposit events asynchronously.&lt;/li&gt;
&lt;li&gt;Reconcile those events into application payment records.&lt;/li&gt;
&lt;li&gt;Run entirely on AWS serverless infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly, it demonstrates a pattern that shows up in many real-world fintech systems: collect payment events quickly, process them safely, and ensure every payment is accounted for.&lt;/p&gt;




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

&lt;p&gt;I deployed the application publicly so you can explore the flow yourself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Application:&lt;/strong&gt; &lt;a href="http://bit.ly/3Qzpd7G" rel="noopener noreferrer"&gt;http://bit.ly/3Qzpd7G&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/Emidowojo/Daya-Payments-Tracker" rel="noopener noreferrer"&gt;https://github.com/Emidowojo/Daya-Payments-Tracker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5e25ewr159z3088mr3uu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5e25ewr159z3088mr3uu.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;br&gt;
The public deployment runs in a safe demonstration mode and showcases the payment lifecycle, webhook handling, event processing, and reconciliation workflow described throughout this article.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a customer pays into your application.&lt;/p&gt;

&lt;p&gt;What happens next?&lt;/p&gt;

&lt;p&gt;Many first implementations look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook arrives
↓
Process payment immediately
↓
Update database
↓
Return success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works until something goes wrong.&lt;/p&gt;

&lt;p&gt;What if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your database is temporarily unavailable?&lt;/li&gt;
&lt;li&gt;A downstream API times out?&lt;/li&gt;
&lt;li&gt;The webhook gets delivered twice?&lt;/li&gt;
&lt;li&gt;Processing takes longer than the provider expects?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you're dealing with failed reconciliations, duplicate records, and support tickets.&lt;/p&gt;

&lt;p&gt;The goal of this project was to avoid those problems from the beginning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture separates payment collection from payment processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser Payment Tracker
        |
        v
Lambda Function URL  &amp;lt;--------  Daya Webhook
        |
        v
Amazon SQS Queue
        |
        v
Worker Lambda
        |
        v
DynamoDB

Secrets Manager -&amp;gt; API Lambda and Worker Lambda

SQS Dead-Letter Queue -&amp;gt; CloudWatch Alarm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core idea is simple:&lt;/p&gt;

&lt;p&gt;The webhook endpoint should acknowledge events quickly.&lt;/p&gt;

&lt;p&gt;Actual reconciliation happens later.&lt;/p&gt;

&lt;p&gt;In this architecture, Daya is the source of payment events. AWS is the reliability layer that helps the application process those events safely.&lt;/p&gt;




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

&lt;p&gt;I chose Daya because this project needed a payment layer that could support both local currency and stablecoin collection through an API.&lt;/p&gt;

&lt;p&gt;Daya Funding Accounts make that possible.&lt;/p&gt;

&lt;p&gt;A funding account represents payment details a business can give to a customer to receive funds. Depending on the rail, that could be an NGN virtual account for bank-transfer collection or a crypto deposit address for stablecoin deposits.&lt;/p&gt;

&lt;p&gt;For this project, Daya provides the collection infrastructure and deposit events that make reconciliation possible. The application then uses AWS to process those events safely and turn them into durable payment records.&lt;/p&gt;

&lt;p&gt;For this implementation, I used:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NGN_VIRTUAL_ACCOUNT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bank-transfer collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CRYPTO_ADDRESS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stablecoin deposits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deposit.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payment notification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Daya-Signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Webhook verification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The flow begins by creating payment details through Daya.&lt;/p&gt;

&lt;p&gt;Customers receive those payment details and make deposits through their preferred payment rail.&lt;/p&gt;

&lt;p&gt;Once funds arrive, Daya emits a webhook event.&lt;/p&gt;

&lt;p&gt;That event becomes the starting point for the application's reconciliation workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relevant Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.daya.co/concepts/funding-accounts" rel="noopener noreferrer"&gt;Funding Accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.daya.co/api-reference/funding-accounts/create-funding-account" rel="noopener noreferrer"&gt;Create Funding Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.daya.co/api-reference/webhooks/events" rel="noopener noreferrer"&gt;Webhook Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.daya.co/api-reference/webhooks/verification" rel="noopener noreferrer"&gt;Webhook Verification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Creating Payment Details
&lt;/h2&gt;

&lt;p&gt;The application supports both bank-transfer collection and crypto collection.&lt;/p&gt;

&lt;p&gt;For bank transfers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create Funding Account
↓
NGN_VIRTUAL_ACCOUNT
↓
Receive account number
↓
Display to customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For crypto deposits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create Funding Account
↓
CRYPTO_ADDRESS
↓
Receive deposit address
↓
Display to customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, no money has moved yet.&lt;/p&gt;

&lt;p&gt;The application is simply preparing a destination where funds can be received.&lt;/p&gt;




&lt;h2&gt;
  
  
  Receiving Webhooks
&lt;/h2&gt;

&lt;p&gt;When a customer completes a payment, Daya sends a webhook event.&lt;/p&gt;

&lt;p&gt;One mistake I see often is treating webhooks as workflows.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;Webhooks are notifications.&lt;/p&gt;

&lt;p&gt;If there is one lesson I would carry into any payment integration, it is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never make the webhook handler do too much.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The webhook endpoint should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify authenticity.&lt;/li&gt;
&lt;li&gt;Persist the event.&lt;/li&gt;
&lt;li&gt;Return success.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else can happen later.&lt;/p&gt;

&lt;p&gt;For this project, the webhook handler follows this flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive webhook
↓
Verify X-Daya-Signature
↓
Push event to SQS
↓
Return HTTP 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;No reconciliation.&lt;/p&gt;

&lt;p&gt;No heavy database operations.&lt;/p&gt;

&lt;p&gt;No external calls.&lt;/p&gt;

&lt;p&gt;Just verification and persistence.&lt;/p&gt;

&lt;p&gt;A simplified version of the webhook handler looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/daya&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Daya-Signature&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;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifyDayaSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid webhook signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the real application, the queue is backed by Amazon SQS in AWS and by a local queue during development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Queue the Webhook?
&lt;/h2&gt;

&lt;p&gt;Once a webhook has been verified, the event is pushed into Amazon SQS.&lt;/p&gt;

&lt;p&gt;This provides several benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster Responses
&lt;/h3&gt;

&lt;p&gt;Webhook providers expect a quick response.&lt;/p&gt;

&lt;p&gt;The API no longer waits for reconciliation logic to finish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retryability
&lt;/h3&gt;

&lt;p&gt;If processing fails, SQS can retry automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure Isolation
&lt;/h3&gt;

&lt;p&gt;A temporary database issue should not cause webhook delivery failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic Buffering
&lt;/h3&gt;

&lt;p&gt;Large bursts of payment activity can be absorbed without overwhelming workers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dead-Letter Queues
&lt;/h3&gt;

&lt;p&gt;Failed events can be isolated and investigated later.&lt;/p&gt;

&lt;p&gt;The queue acts as a safety layer between incoming payment events and business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Processing Payments Safely
&lt;/h2&gt;

&lt;p&gt;A worker Lambda consumes events from SQS.&lt;/p&gt;

&lt;p&gt;This is where the application reconciliation happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQS Event
↓
Validate payload
↓
Check idempotency
↓
Store payment record
↓
Mark event processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker becomes responsible for turning Daya payment notifications into durable application records.&lt;/p&gt;

&lt;p&gt;This separation makes the system significantly easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling Duplicate Events
&lt;/h2&gt;

&lt;p&gt;Payment systems should assume every event can arrive more than once.&lt;/p&gt;

&lt;p&gt;Not because something is broken.&lt;/p&gt;

&lt;p&gt;Because retries are a feature of reliable systems.&lt;/p&gt;

&lt;p&gt;To avoid duplicate processing, the worker stores processed webhook IDs.&lt;/p&gt;

&lt;p&gt;Before handling an event, it checks whether that ID already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook Event
↓
Has event been processed?
 ├─ Yes → Skip
 └─ No → Process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This property is known as idempotency.&lt;/p&gt;

&lt;p&gt;Without it, duplicate webhook deliveries could create duplicate payment records.&lt;/p&gt;

&lt;p&gt;With it, the same event can safely be delivered multiple times.&lt;/p&gt;

&lt;p&gt;That is the difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We received a webhook"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We safely reconciled a payment."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Storing Payment State
&lt;/h2&gt;

&lt;p&gt;DynamoDB serves as the application's reconciliation store, not as a replacement for Daya's payment records.&lt;/p&gt;

&lt;p&gt;The application stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Funding accounts&lt;/li&gt;
&lt;li&gt;Payment records&lt;/li&gt;
&lt;li&gt;Processed webhook IDs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having a dedicated record of processed events makes idempotent processing straightforward.&lt;/p&gt;

&lt;p&gt;It also provides a clear audit trail when troubleshooting payment issues.&lt;/p&gt;




&lt;h2&gt;
  
  
  Managing Secrets
&lt;/h2&gt;

&lt;p&gt;Payment systems inevitably require credentials.&lt;/p&gt;

&lt;p&gt;For this build, I used AWS Secrets Manager to store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daya API keys&lt;/li&gt;
&lt;li&gt;Daya webhook secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither the application code nor deployment artifacts contain sensitive values.&lt;/p&gt;

&lt;p&gt;Both the API Lambda and worker Lambda retrieve secrets at runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitoring and Operations
&lt;/h2&gt;

&lt;p&gt;Reliable systems need visibility.&lt;/p&gt;

&lt;p&gt;CloudWatch provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application logs&lt;/li&gt;
&lt;li&gt;Worker logs&lt;/li&gt;
&lt;li&gt;Error tracking&lt;/li&gt;
&lt;li&gt;Dead-letter queue alarms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If events begin accumulating in the dead-letter queue, CloudWatch can raise an alert so operators know reconciliation requires attention.&lt;/p&gt;

&lt;p&gt;This is one of those operational details that rarely appears in architecture diagrams but becomes invaluable in production.&lt;/p&gt;




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

&lt;p&gt;The project is deployed using AWS CDK and a serverless architecture built around Lambda, SQS, DynamoDB, Secrets Manager, and CloudWatch.&lt;/p&gt;

&lt;p&gt;The deployment process provisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application endpoints&lt;/li&gt;
&lt;li&gt;Webhook endpoints&lt;/li&gt;
&lt;li&gt;Event queues&lt;/li&gt;
&lt;li&gt;Dead-letter queues&lt;/li&gt;
&lt;li&gt;DynamoDB tables&lt;/li&gt;
&lt;li&gt;Secrets storage&lt;/li&gt;
&lt;li&gt;Monitoring resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the goals of the project was to demonstrate that reliable payment processing does not require managing servers. By combining event-driven AWS services, the operational burden remains low while reliability remains high.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Containers?
&lt;/h2&gt;

&lt;p&gt;Because I am also active in the AWS Containers community, I included an ECS Fargate deployment path in the project as well.&lt;/p&gt;

&lt;p&gt;Functionally, both deployments achieve the same outcome.&lt;/p&gt;

&lt;p&gt;The difference is operational.&lt;/p&gt;

&lt;p&gt;The serverless version is cheaper and simpler to maintain.&lt;/p&gt;

&lt;p&gt;The ECS version provides a stronger container-focused architecture and may fit teams already standardizing on containers.&lt;/p&gt;

&lt;p&gt;For educational projects, I would start with serverless and treat the container deployment as an advanced follow-up.&lt;/p&gt;




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

&lt;p&gt;Building this project reinforced a few lessons that apply far beyond this payment tracker.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Webhook Handlers Should Be Boring
&lt;/h3&gt;

&lt;p&gt;When I first started working with event-driven systems, I was tempted to do all the work inside the webhook handler.&lt;/p&gt;

&lt;p&gt;That approach works until something downstream becomes unavailable.&lt;/p&gt;

&lt;p&gt;The more responsibility a webhook endpoint has, the more opportunities it has to fail.&lt;/p&gt;

&lt;p&gt;In this project, the webhook handler has one job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the request.&lt;/li&gt;
&lt;li&gt;Persist the event.&lt;/li&gt;
&lt;li&gt;Return success.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else happens asynchronously.&lt;/p&gt;

&lt;p&gt;Keeping the handler simple makes the system more reliable and easier to troubleshoot.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Queues Are an Investment in Reliability
&lt;/h3&gt;

&lt;p&gt;It's easy to look at SQS and think it adds complexity.&lt;/p&gt;

&lt;p&gt;In reality, it removes complexity from the parts of the system that matter most.&lt;/p&gt;

&lt;p&gt;The queue absorbs traffic spikes, handles retries, and creates a buffer between incoming payment events and business logic.&lt;/p&gt;

&lt;p&gt;Without the queue, every temporary downstream failure becomes a payment-processing problem.&lt;/p&gt;

&lt;p&gt;With the queue, failures become manageable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Idempotency Is Not Optional
&lt;/h3&gt;

&lt;p&gt;One of the most important lessons in payment systems is that duplicate events are normal.&lt;/p&gt;

&lt;p&gt;Providers retry webhooks.&lt;/p&gt;

&lt;p&gt;Networks fail.&lt;/p&gt;

&lt;p&gt;Clients reconnect.&lt;/p&gt;

&lt;p&gt;The question is not whether duplicates will happen.&lt;/p&gt;

&lt;p&gt;The question is whether your system can handle them safely.&lt;/p&gt;

&lt;p&gt;Designing for idempotency from the start is much easier than trying to retrofit it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Reliability Is an Architecture Decision
&lt;/h3&gt;

&lt;p&gt;Reliable systems are rarely the result of a single service or framework.&lt;/p&gt;

&lt;p&gt;Reliability comes from the choices made between components.&lt;/p&gt;

&lt;p&gt;The queue, the worker, the dead-letter queue, the monitoring, and the idempotency checks all contribute to a system that can recover gracefully when things go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Observability Matters More Than Expected
&lt;/h3&gt;

&lt;p&gt;One thing that became clear while building this project is that debugging payment systems without visibility is frustrating.&lt;/p&gt;

&lt;p&gt;Logs, metrics, and alerts are not features users see, but they are often the difference between quickly resolving an issue and spending hours trying to understand what happened.&lt;/p&gt;

&lt;p&gt;CloudWatch ended up being just as important as the business logic itself because it provided confidence that payment events were flowing through the system correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before Production
&lt;/h2&gt;

&lt;p&gt;Before adapting this pattern for a real customer-facing product, I would recommend adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication and authorization&lt;/li&gt;
&lt;li&gt;Custom domains and TLS management&lt;/li&gt;
&lt;li&gt;Structured tracing&lt;/li&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Alarm notifications&lt;/li&gt;
&lt;li&gt;Production reconciliation queries&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Audit logging&lt;/li&gt;
&lt;li&gt;Separation of testing and production environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is intentionally simple, but the reliability patterns scale well beyond this example.&lt;/p&gt;




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

&lt;p&gt;Building payment systems taught me that reliability rarely comes from a single API call.&lt;/p&gt;

&lt;p&gt;It comes from the decisions made around that call.&lt;/p&gt;

&lt;p&gt;How events are received.&lt;/p&gt;

&lt;p&gt;How failures are handled.&lt;/p&gt;

&lt;p&gt;How retries are processed.&lt;/p&gt;

&lt;p&gt;How operators gain confidence that the system reflects reality.&lt;/p&gt;

&lt;p&gt;Daya makes it possible to create payment destinations and receive deposit events.&lt;/p&gt;

&lt;p&gt;AWS helps the application process those events reliably and reconcile them into its own records.&lt;/p&gt;

&lt;p&gt;Together, they provide a practical blueprint for building payment systems that users and operators can trust.&lt;/p&gt;

</description>
      <category>dayaapi</category>
      <category>aws</category>
      <category>fintech</category>
      <category>stablecoins</category>
    </item>
    <item>
      <title>Building a Student REST API with Flask and Docker</title>
      <dc:creator>Emidowojo</dc:creator>
      <pubDate>Wed, 16 Apr 2025 22:46:57 +0000</pubDate>
      <link>https://dev.to/techondiapers/building-a-student-rest-api-with-flask-and-docker-42en</link>
      <guid>https://dev.to/techondiapers/building-a-student-rest-api-with-flask-and-docker-42en</guid>
      <description>&lt;p&gt;Hey there! I recently built a REST API to manage student records using Flask, Flask-SQLAlchemy, and SQLite, and shared it in my last post. Now, I have taken it to the next level by containerizing it with Docker, making it portable and consistent across machines. &lt;/p&gt;

&lt;p&gt;In this post, I will walk you through how I Dockerized my API, including the mistakes I made and how I fixed them, so you can follow along or avoid my pitfalls.&lt;/p&gt;




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

&lt;p&gt;Docker packages your app with its dependencies into a container, ensuring it runs the same everywhere, your laptop, a server, or the cloud. For my Student API, this meant no more “it works on my machine” excuses. &lt;/p&gt;

&lt;p&gt;Let’s dive into the steps I took, bugs and all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Writing the Dockerfile
&lt;/h2&gt;

&lt;p&gt;I started by creating a &lt;code&gt;Dockerfile&lt;/code&gt; in my project folder (&lt;code&gt;student-api-new/&lt;/code&gt;) to define how to build the Docker image. I used a &lt;strong&gt;multi-stage build&lt;/strong&gt; to keep the image small: one stage for installing dependencies, another for running the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Build dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use the slim version of Python 3.13 as base image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.13-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory in the container&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Update package list&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update

&lt;span class="c"&gt;# Install build tools like gcc, then clean up&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; gcc &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get autoremove &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Copy the requirements file into the container&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;

&lt;span class="c"&gt;# Install required Python packages without caching&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Install Gunicorn explicitly for running the app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nv"&gt;gunicorn&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;23.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stage 2: Runtime image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start again from a clean slim Python image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.13-slim&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory again for final image&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy installed Python packages from the builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages&lt;/span&gt;

&lt;span class="c"&gt;# Copy Gunicorn from builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn&lt;/span&gt;

&lt;span class="c"&gt;# Copy your actual Flask app code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app/ ./app/&lt;/span&gt;

&lt;span class="c"&gt;# Create non-root user for better security&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appuser /app

&lt;span class="c"&gt;# Switch to the new user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="c"&gt;# Expose the port the app runs on&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="c"&gt;# Set the Flask app environment variable&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FLASK_APP=app&lt;/span&gt;

&lt;span class="c"&gt;# Command to run the Flask app with Gunicorn&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["gunicorn", "--bind", "0.0.0.0:5000", "app:create_app()"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Stage Builds&lt;/strong&gt;: The builder stage handles heavy lifting (like installing gcc), but the final image only includes essentials, keeping it around 235MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slim Base Image&lt;/strong&gt;: &lt;code&gt;python:3.13-slim&lt;/code&gt; is lighter than the full Python image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Root User&lt;/strong&gt;: Using &lt;code&gt;appuser&lt;/code&gt; improves security for production.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Adding a &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To reduce the image size, I created a &lt;code&gt;.dockerignore&lt;/code&gt; file to exclude unnecessary files, like my virtual environment and test folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.venv/
.env
__pycache__/
*.pyc
students.db
.git/
.gitignore
tests/
README.md
Makefile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensured only the app code and dependencies went into the container, saving space.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Building the Docker Image
&lt;/h2&gt;

&lt;p&gt;I built the image with a semantic version tag (avoiding &lt;code&gt;latest&lt;/code&gt;, which I learned is a no-no for reproducibility):&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="c"&gt;# Build the Docker image and tag it&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; student-api:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Running the Container
&lt;/h2&gt;

&lt;p&gt;To run the API, I used a command that maps port 5000 and loads environment variables from a &lt;code&gt;.env&lt;/code&gt; file:&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="c"&gt;# Run the container in detached mode and pass in environment variables&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env student-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contents of my &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FLASK_ENV=development
DATABASE_URL=sqlite:///students.db
SECRET_KEY=mysecretkey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Testing the API
&lt;/h2&gt;

&lt;p&gt;With the container running, I tested it using &lt;code&gt;curl&lt;/code&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="c"&gt;# Check if the API is healthy&lt;/span&gt;
curl http://localhost:5000/api/v1/healthcheck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Output:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"healthy"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a student record&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:5000/api/v1/students &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;'{"name": "Alice", "age": 20}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output:
&lt;/h3&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&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;Seeing those responses felt like such a huge win considering all the errors I encountered 🥲 My API was finally alive in Docker.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Updating Documentation
&lt;/h2&gt;

&lt;p&gt;I updated my &lt;code&gt;README.md&lt;/code&gt; to include Docker instructions, so anyone could try it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup (Docker)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Docker installed (&lt;code&gt;docker --version&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Build the Image
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make docker-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Run the Container
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;p&gt;I also added &lt;code&gt;Makefile&lt;/code&gt; targets to simplify things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Makefile for building and running Docker image
&lt;/span&gt;
&lt;span class="nl"&gt;docker-build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    docker build &lt;span class="nt"&gt;-t&lt;/span&gt; student-api:1.0.0 .

&lt;span class="nl"&gt;docker-run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env student-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I Learned&lt;/strong&gt;: Good docs make your project accessible. The Makefile was a lifesaver for quick commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Committing to GitHub
&lt;/h2&gt;

&lt;p&gt;I wanted to share my Docker setup on GitHub, so I committed the Dockerfile and other files:&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="c"&gt;# Add Docker setup files to Git&lt;/span&gt;
git add Dockerfile .gitignore

&lt;span class="c"&gt;# Commit with message&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Docker setup for Student API"&lt;/span&gt;

&lt;span class="c"&gt;# Push to GitHub&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Errors I Ran Into (and How I Fixed Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Slow Build Time (17+ Minutes)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error&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;[+] Building 1050.2s (8/12)
=&amp;gt; [builder 4/4] RUN apt-get update &amp;amp;&amp;amp; apt-get install ... 970.8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt;: My internet connection was slow, which bogged down &lt;code&gt;apt-get&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&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="c"&gt;# Check network speed&lt;/span&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null http://deb.debian.org/debian/dists/bookworm/InRelease

&lt;span class="c"&gt;# Clear Docker build cache&lt;/span&gt;
docker builder prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also split &lt;code&gt;apt-get update&lt;/code&gt; into a separate &lt;code&gt;RUN&lt;/code&gt; to cache it better. My build time dropped to ~1–2 minutes afterwards.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Gunicorn Not Found
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error&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;docker: Error response from daemon: ... exec: "gunicorn": executable file not found in $PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt;: I forgot to add &lt;code&gt;gunicorn&lt;/code&gt; to &lt;code&gt;requirements.txt&lt;/code&gt;, and didn’t rebuild properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&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;# requirements.txt
Flask==3.1.0
Flask-SQLAlchemy==3.1.1
gunicorn==23.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nv"&gt;gunicorn&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;23.0.0
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rebuild clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; student-api:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Container Conflict When Rebuilding
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error&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;Error response from daemon: conflict: unable to delete student-api:1.0.0 ... container 25ae709a77aa is using it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix&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="c"&gt;# List all containers (even stopped)&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# Remove the stopped container&lt;/span&gt;
docker &lt;span class="nb"&gt;rm &lt;/span&gt;25ae709a77aa

&lt;span class="c"&gt;# Remove the image&lt;/span&gt;
docker rmi student-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Git Ignoring Dockerfile
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error&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;The following paths are ignored by one of your .gitignore files: Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Removed &lt;code&gt;Dockerfile*&lt;/code&gt; from &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.venv/
.env
__pycache__/
*.pyc
students.db
.git/
.gitignore
tests/
docker-compose*
*.dockerignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then committed successfully.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. VS Code Warning About Gunicorn
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error&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;Package gunicorn is not installed in the selected environment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt;: I hadn't installed &lt;code&gt;gunicorn&lt;/code&gt; in my local &lt;code&gt;.venv/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&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="c"&gt;# Activate virtual environment&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

&lt;span class="c"&gt;# Install dependencies locally&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;These errors taught me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rebuild Images&lt;/strong&gt;: Always rebuild after changing &lt;code&gt;requirements.txt&lt;/code&gt; or &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check Logs&lt;/strong&gt;: &lt;code&gt;docker logs &amp;lt;container-id&amp;gt;&lt;/code&gt; is your best debugging buddy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean Up&lt;/strong&gt;: Remove old containers/images to avoid conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document Everything&lt;/strong&gt;: Clear steps save time later.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;Dockerizing my API was a huge win, as it is now portable and production-ready. My next steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pushing &lt;code&gt;student-api:1.0.0&lt;/code&gt; to Docker Hub to share with others.&lt;/li&gt;
&lt;li&gt;Adding endpoints like &lt;code&gt;GET /students&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Writing more tests to ensure reliability.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading! If you’re Dockerizing your own project, I hope my mistakes save you some headaches. Drop a comment with your tips or questions, I’d love to hear them ✨&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
    <item>
      <title>💡 Build Along with Me: A Beginner’s Guide to Creating a Student API Using Flask</title>
      <dc:creator>Emidowojo</dc:creator>
      <pubDate>Sat, 12 Apr 2025 00:20:35 +0000</pubDate>
      <link>https://dev.to/techondiapers/build-along-with-me-a-beginners-guide-to-creating-a-student-api-using-flask-5kn</link>
      <guid>https://dev.to/techondiapers/build-along-with-me-a-beginners-guide-to-creating-a-student-api-using-flask-5kn</guid>
      <description>&lt;p&gt;Today on my journey to gaining DevOps Mastery, I Built a Student REST API with Flask. In this guide, I’ll walk you through how I created a &lt;strong&gt;REST API&lt;/strong&gt; from scratch. No fancy frameworks or prior knowledge is required—just some curiosity and a willingness to debug. Let’s build it together, step-by-step, with all the highs and lows I hit along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;Before we start, here’s what you’ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Computer:&lt;/strong&gt; Any machine with Python 3 (Windows, Linux, or Mac) works fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic Terminal Skills:&lt;/strong&gt; If you can type commands like &lt;code&gt;cd&lt;/code&gt; or &lt;code&gt;mkdir&lt;/code&gt;, you’re set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python Installed:&lt;/strong&gt; I had Python 3.13—check with &lt;code&gt;python3 --version&lt;/code&gt; in your terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code (Optional):&lt;/strong&gt; It’s my editor of choice, but any text editor (like Notepad) will do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patience:&lt;/strong&gt; I ran into errors (like “db not defined”), but I’ll show you how to fix them!&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;Introduction to REST APIs&lt;/li&gt;
&lt;li&gt;Step-by-Step Guide to Building the Student API&lt;/li&gt;
&lt;li&gt;Key Concepts: Flask, SQLAlchemy, and Testing&lt;/li&gt;
&lt;li&gt;Troubleshooting Common Issues&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introduction to REST APIs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a REST API?
&lt;/h3&gt;

&lt;p&gt;Think of a REST API as a librarian for a student database. You can ask it to add a student, list all students, update a record, or delete one—all through web commands. It’s like a website, but instead of pages, it gives you data (like &lt;code&gt;{"name": "Alice", "age": 20}&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is this useful?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility:&lt;/strong&gt; Use it from a browser, app, or script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning:&lt;/strong&gt; Teaches you web basics in a fun way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-World Skills:&lt;/strong&gt; APIs power tons of apps—like your favorite social media.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, I’ll show you how I built the API to handle CRUD (Create, Read, Update, Delete) operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Here’s how I built my API, command by command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Setting Up the Project
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Creating the Folder and Virtual Environment
&lt;/h4&gt;

&lt;p&gt;On my terminal:&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="c"&gt;# Create a new folder for the project&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;student-api-new
&lt;span class="c"&gt;# Move into the folder&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;student-api-new
&lt;span class="c"&gt;# Create a virtual environment (a "toolbox" for project tools)&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="c"&gt;# Activate the virtual environment to use its tools&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Makes a folder and a “toolbox” (virtual environment) to keep my project’s tools separate. Activating it with &lt;code&gt;source&lt;/code&gt; puts me in the toolbox.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Installing Tools
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Flask (web framework), Flask-SQLAlchemy (database), python-dotenv (settings), and pytest (testing)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;flask flask-sqlalchemy python-dotenv pytest
&lt;span class="c"&gt;# Save the installed tools to a list for others to use&lt;/span&gt;
pip freeze &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Adds Flask (web framework), Flask-SQLAlchemy (database helper), python-dotenv (settings loader), and pytest (testing tool). &lt;code&gt;requirements.txt&lt;/code&gt; lists them for later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 2: Building the Structure
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Setting Up Files
&lt;/h4&gt;

&lt;p&gt;I used VS Code to create this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;student-api-new/
├── Makefile          # Instructions for running the app
├── README.md         # Project guide
├── requirements.txt  # List of tools
├── .env              # Secret settings
├── .gitignore        # Files to ignore in Git
├── app/              # Main app code
│   ├── __init__.py   # App setup
│   ├── models.py     # Student definition
│   ├── routes.py     # Web commands
│   └── config.py     # App settings
├── tests/            # Test code
│   └── test_api.py   # Tests for the API
└── .venv/            # Virtual environment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How:&lt;/strong&gt; Right-click in VS Code’s Explorer, pick “New File” or “New Folder,” and name them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 3: Coding the API
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Configuration (&lt;code&gt;app/config.py&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;.env&lt;/code&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="c"&gt;# Set Flask to development mode for easier debugging&lt;/span&gt;
&lt;span class="nv"&gt;FLASK_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;development
&lt;span class="c"&gt;# Define the database location (SQLite file)&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite:///students.db
&lt;span class="c"&gt;# A secret key for security&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecretkey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;app/config.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="c1"&gt;# Import os to access environment variables
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="c1"&gt;# Import dotenv to load .env file
&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="c1"&gt;# Load environment variables from .env
&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;# Define configuration settings for the app
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Set database location from .env
&lt;/span&gt;    &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&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;DATABASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Disable a warning for performance
&lt;/span&gt;    &lt;span class="n"&gt;SQLALCHEMY_TRACK_MODIFICATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="c1"&gt;# Set secret key from .env for security
&lt;/span&gt;    &lt;span class="n"&gt;SECRET_KEY&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;SECRET_KEY&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why:&lt;/strong&gt; Keeps secrets (like the database location) safe and separate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The App Setup (&lt;code&gt;app/__init__.py&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import logging to track app events
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="c1"&gt;# Import Flask to create the web app
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="c1"&gt;# Import Config for settings
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;
&lt;span class="c1"&gt;# Import db for the database
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;

&lt;span class="c1"&gt;# Set up logging to show info messages
&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;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&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="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Create a logger for this module
&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="c1"&gt;# Function to create and configure the Flask app
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_app&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Initialize Flask app
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&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="c1"&gt;# Load settings from Config
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Connect database to app
&lt;/span&gt;    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Create database tables
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Log that tables were created
&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;Database tables created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Import and set up routes
&lt;/span&gt;    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.routes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;init_routes&lt;/span&gt;
    &lt;span class="nf"&gt;init_routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Return the configured app
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Starts the Flask app and sets up the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Student Model (&lt;code&gt;app/models.py&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import SQLAlchemy for database management
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask_sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;

&lt;span class="c1"&gt;# Create database object
&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SQLAlchemy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Define Student table structure
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Student&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Primary key column for unique IDs
&lt;/span&gt;    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Name column, can't be empty
&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Age column, can't be empty
&lt;/span&gt;    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert student data to a dictionary for API responses
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;self&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;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Defines what a “student” looks like in the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  API Routes (&lt;code&gt;app/routes.py&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import logging for tracking events
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="c1"&gt;# Import Flask tools for handling requests
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;
&lt;span class="c1"&gt;# Import database and Student model
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt;

&lt;span class="c1"&gt;# Create a logger for this module
&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="c1"&gt;# Function to define all API routes
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init_routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Healthcheck endpoint to test if API is running
&lt;/span&gt;    &lt;span class="nd"&gt;@app.route&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/v1/healthcheck&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&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;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;healthcheck&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="c1"&gt;# Return a JSON response with status
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&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;healthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

    &lt;span class="c1"&gt;# Endpoint to add a new student
&lt;/span&gt;    &lt;span class="nd"&gt;@app.route&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/v1/students&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&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;POST&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;add_student&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="c1"&gt;# Get JSON data from request
&lt;/span&gt;        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Create a new student object
&lt;/span&gt;        &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Student&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;data&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;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age&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 student to database
&lt;/span&gt;        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Save changes to database
&lt;/span&gt;        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Log the addition
&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Student &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; added with ID &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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="c1"&gt;# Return student data as JSON with status 201 (created)
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;
    &lt;span class="c1"&gt;# More routes for GET, PUT, DELETE...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Adds web commands like “add a student.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Makefile
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Define targets that aren't files
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;install run test&lt;/span&gt;

&lt;span class="c"&gt;# Target to install dependencies
&lt;/span&gt;&lt;span class="nl"&gt;install&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;# Install tools listed in requirements.txt&lt;/span&gt;
    .venv/bin/pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Target to run the app
&lt;/span&gt;&lt;span class="nl"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;# Start Flask server&lt;/span&gt;
    flask run

&lt;span class="c"&gt;# Target to run tests
&lt;/span&gt;&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;# Run pytest with verbose output&lt;/span&gt;
    .venv/bin/pytest &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pitfall:&lt;/strong&gt; Use &lt;strong&gt;tabs&lt;/strong&gt; before commands, not spaces or it fails&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 4: Testing the API
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Manual Testing
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Tell Flask where the app is&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app
&lt;span class="c"&gt;# Start the app&lt;/span&gt;
make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send a POST request to add a student&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:5000/api/v1/students &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "Alice", "age": 20}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Output: &lt;code&gt;{"id": 1, "name": "Alice", "age": 20}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Automated Testing (&lt;code&gt;tests/test_api.py&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import pytest for testing
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="c1"&gt;# Import sys and os for path adjustments
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="c1"&gt;# Add project root to Python path
&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&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="n"&gt;path&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;..&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;# Import app setup function
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_app&lt;/span&gt;
&lt;span class="c1"&gt;# Import database object
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;

&lt;span class="c1"&gt;# Define a fixture to set up test client
&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Create app instance
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Enable testing mode
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TESTING&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="c1"&gt;# Create a test client
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Use app context for database operations
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# Create database tables
&lt;/span&gt;            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Yield client for tests
&lt;/span&gt;        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

&lt;span class="c1"&gt;# Test adding a student
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_add_student&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Send POST request with student data
&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&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/v1/students&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;name&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;Alice&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;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c1"&gt;# Check if status code is 201 (created)
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;
    &lt;span class="c1"&gt;# Check if response name matches input
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run:&lt;/strong&gt; &lt;code&gt;make test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pitfall:&lt;/strong&gt; Forgot to import &lt;code&gt;db&lt;/code&gt;—fixed it and it passed!&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  API Documentation
&lt;/h2&gt;

&lt;p&gt;Now that we've built and tested our API, here's a complete reference of all available endpoints and how to use them.&lt;/p&gt;

&lt;h4&gt;
  
  
  🩺 Health Check
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;GET&lt;/strong&gt; &lt;code&gt;/api/v1/healthcheck&lt;/code&gt; Checks if the API is running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&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;"healthy"&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;h4&gt;
  
  
  🎓 Create a New Student
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;POST&lt;/strong&gt; &lt;code&gt;/api/v1/students&lt;/code&gt; Adds a student to the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request Body:&lt;/strong&gt;&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;"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;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&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;&lt;strong&gt;Success Response (201):&lt;/strong&gt;&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="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;"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;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&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;&lt;strong&gt;Error (400):&lt;/strong&gt;&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid input"&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;h4&gt;
  
  
  📋 Get All Students
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;GET&lt;/strong&gt; &lt;code&gt;/api/v1/students&lt;/code&gt; Retrieves all students.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&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="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;"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;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&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;h4&gt;
  
  
  🔍 Get a Student by ID
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;GET&lt;/strong&gt; &lt;code&gt;/api/v1/students/&amp;lt;id&amp;gt;&lt;/code&gt; Fetches a student using their ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Success (200):&lt;/strong&gt;&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="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;"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;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&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;&lt;strong&gt;Error (404):&lt;/strong&gt;&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Student not found"&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;h4&gt;
  
  
  📝 Update a Student
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;PUT&lt;/strong&gt; &lt;code&gt;/api/v1/students/&amp;lt;id&amp;gt;&lt;/code&gt; Updates a student's details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request Body:&lt;/strong&gt;&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;"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;"Alicia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&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;&lt;strong&gt;Success (200):&lt;/strong&gt;&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="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;"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;"Alicia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&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;&lt;strong&gt;Error (404):&lt;/strong&gt;&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Student not found"&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;h4&gt;
  
  
  ❌ Delete a Student
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;DELETE&lt;/strong&gt; &lt;code&gt;/api/v1/students/&amp;lt;id&amp;gt;&lt;/code&gt; Deletes a student record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Success (204):&lt;/strong&gt; No content returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error (404):&lt;/strong&gt;&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Student not found"&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;h4&gt;
  
  
  ✅ Common Status Codes
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;td&gt;Created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;204&lt;/td&gt;
&lt;td&gt;No Content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;Bad Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;Not Found&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; You can test all these endpoints using &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; or &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;cURL&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Concepts: Flask, SQLAlchemy, and Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flask:&lt;/strong&gt; A lightweight tool to build web apps. It’s like the frame of our “student library.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLAlchemy:&lt;/strong&gt; Manages the database, like a filing cabinet for student records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing with pytest:&lt;/strong&gt; Checks if the app works without manual effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, they make a simple, working API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Makefile Error (“missing separator”):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Issue:&lt;/strong&gt; Used spaces instead of tabs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; Retyped with &lt;strong&gt;tabs&lt;/strong&gt;—worked like magic!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;“Not Found” on Healthcheck:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Issue:&lt;/strong&gt; App wasn’t running or routes weren’t loaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; Checked &lt;code&gt;make run&lt;/code&gt; and &lt;code&gt;routes.py&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;“db not defined” in Tests:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Issue:&lt;/strong&gt; Missed importing &lt;code&gt;db&lt;/code&gt; in &lt;code&gt;test_api.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; Added &lt;code&gt;from app.models import db&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




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

&lt;p&gt;I built a Student REST API from nothing—adding students, and testing it. It’s not perfect, but it works, and I learned so much. From “missing separator” to “db not defined,” every error taught me something new. I hope you learnt something insightful from this. Happy coding!&lt;/p&gt;

</description>
      <category>sre</category>
      <category>webdev</category>
      <category>flask</category>
      <category>python</category>
    </item>
    <item>
      <title>Shhh, It's a Secret! Using Pulumi ESC &amp; AWS Lambda for Secure Secrets Management</title>
      <dc:creator>Emidowojo</dc:creator>
      <pubDate>Thu, 03 Apr 2025 02:09:49 +0000</pubDate>
      <link>https://dev.to/techondiapers/shhh-its-a-secret-using-pulumi-esc-aws-lambda-for-secure-secrets-management-1kbn</link>
      <guid>https://dev.to/techondiapers/shhh-its-a-secret-using-pulumi-esc-aws-lambda-for-secure-secrets-management-1kbn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pulumi"&gt;Pulumi Deploy and Document Challenge&lt;/a&gt;: Shhh, It's a Secret!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09s0rz69llqpixc2edwb.jpeg" 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%2F09s0rz69llqpixc2edwb.jpeg" alt="Image description: Lambda and ESC in Action" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Hey there! I'm excited to share &lt;strong&gt;Shhh, It's a Secret!&lt;/strong&gt;, a beginner-friendly project where I built an AWS Lambda function using Pulumi. This Lambda grabs a secret (in my case, &lt;code&gt;fake-api-key-xyz123&lt;/code&gt;) from Pulumi ESC (Environments, Secrets, and Configuration)—a super cool tool for keeping sensitive stuff safe. While it's not a static website itself, imagine it as a helper for one: it could securely pass secrets to a fast static site without exposing them in the code. I used Pulumi to set up everything, ESC to manage the secret, and Node.js to make the Lambda work—all wrapped up in a GitHub repo with a clear README for anyone to follow along.&lt;/p&gt;

&lt;p&gt;Here's what's inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pulumi Stack:&lt;/strong&gt; Sets up an IAM role and the Lambda function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ESC Environment:&lt;/strong&gt; Holds my secret, &lt;code&gt;myApiKey&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Function:&lt;/strong&gt; Pulls the secret using the &lt;code&gt;@pulumi/esc-sdk&lt;/code&gt; and logs it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't worry if this sounds complex—I'll walk you through every step so you can build it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo Link
&lt;/h2&gt;

&lt;p&gt;Since this is a Lambda (a serverless function) and not a website with a URL, there's no "live demo" to click. But you can try it yourself in your AWS account! Here's how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Grab the code from my repo (see below).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pulumi up&lt;/code&gt; to deploy it.&lt;/li&gt;
&lt;li&gt;Head to AWS Console &amp;gt; Lambda &amp;gt; &lt;code&gt;secretFetcher&lt;/code&gt; &amp;gt; Test, and run a test event (just use &lt;code&gt;{}&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Check CloudWatch logs—you should see "Secret fetched: fake-api-key-xyz123".&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;

&lt;p&gt;Check out my GitHub repo: &lt;strong&gt;&lt;a href="https://github.com/Emidowojo/pulumi-secret-demo" rel="noopener noreferrer"&gt;Emidowojo/pulumi-secret-demo&lt;/a&gt;&lt;/strong&gt;. It's got all the code and a &lt;code&gt;README.md&lt;/code&gt;. Here's a peek:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Pulumi Secret Demo&lt;/span&gt;
This project shows you how to fetch a secret from Pulumi ESC with an AWS Lambda.
&lt;span class="gu"&gt;## Setup&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Install Pulumi and AWS CLI (don't worry, I'll explain how below!).
&lt;span class="p"&gt;2.&lt;/span&gt; Run &lt;span class="sb"&gt;`pulumi login`&lt;/span&gt; and &lt;span class="sb"&gt;`aws configure`&lt;/span&gt;.
&lt;span class="p"&gt;3.&lt;/span&gt; Clone this: &lt;span class="sb"&gt;`git clone https://github.com/Emidowojo/pulumi-secret-demo`&lt;/span&gt;.
&lt;span class="p"&gt;4.&lt;/span&gt; Go in: &lt;span class="sb"&gt;`cd pulumi-secret-demo &amp;amp;&amp;amp; npm install`&lt;/span&gt;.
&lt;span class="p"&gt;5.&lt;/span&gt; Set up ESC: Make an environment &lt;span class="sb"&gt;`Emidowojo/pulumi-secret-demo/my-secrets`&lt;/span&gt; with &lt;span class="sb"&gt;`myApiKey: fake-api-key-xyz123`&lt;/span&gt;.
&lt;span class="p"&gt;6.&lt;/span&gt; Add your token: &lt;span class="sb"&gt;`pulumi config set pulumiAccessToken &amp;lt;your-token&amp;gt; --secret`&lt;/span&gt;.
&lt;span class="p"&gt;7.&lt;/span&gt; Deploy: &lt;span class="sb"&gt;`pulumi up`&lt;/span&gt;.
&lt;span class="gu"&gt;## Testing&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Go to AWS Console &amp;gt; Lambda &amp;gt; &lt;span class="sb"&gt;`secretFetcher`&lt;/span&gt; &amp;gt; Test.
&lt;span class="p"&gt;-&lt;/span&gt; Look in CloudWatch logs for "Secret fetched: fake-api-key-xyz123".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Journey
&lt;/h2&gt;

&lt;p&gt;While building it, I hit bumps, learned so much, and came out with a working project. Let me take you through it step-by-step, so you can follow along (and avoid my mistakes).&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 1: Getting Started with Pulumi and ESC
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I Did:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tools First:&lt;/strong&gt; I installed Pulumi with &lt;code&gt;npm install -g @pulumi/pulumi&lt;/code&gt; (it's a command-line tool for cloud stuff) and AWS CLI via Homebrew (&lt;code&gt;brew install awscli&lt;/code&gt;) on my MacBook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging In:&lt;/strong&gt; Ran &lt;code&gt;pulumi login&lt;/code&gt; to connect to app.pulumi.com (you'll need an account—free to sign up!). Then &lt;code&gt;aws configure&lt;/code&gt; to add my AWS Access Key, Secret Key, and region (us-east-1).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New Project:&lt;/strong&gt; Made a folder with &lt;code&gt;mkdir pulumi-secret-demo &amp;amp;&amp;amp; cd pulumi-secret-demo&lt;/code&gt;, then started a Pulumi project with &lt;code&gt;pulumi new aws-typescript&lt;/code&gt;. This gave me a starter index.ts file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ESC Setup:&lt;/strong&gt; Went to app.pulumi.com, clicked ESC &amp;gt; New Environment, named it &lt;code&gt;Emidowojo/pulumi-secret-demo/my-secrets&lt;/code&gt;, and added:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;myApiKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fake-api-key-xyz123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Token Time:&lt;/strong&gt; Got my Pulumi access token from User Settings and ran:&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;export &lt;/span&gt;&lt;span class="nv"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pul-&lt;span class="k"&gt;**************************************************************&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What Went Wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I forgot the token at first—&lt;code&gt;pulumi up&lt;/code&gt; failed with "Not authenticated." Setting the token fixed it fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; ESC is awesome for secrets—it's like a safe for your API keys. If you're new, try to double-check your logins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 2: Making the Lambda and Fixing Bugs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I Did:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pulumi Code (index.ts):&lt;/strong&gt; Wrote this to create a Lambda and IAM role:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pulumi/pulumi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Import Pulumi for coding cloud stuff&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pulumi/aws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Import AWS tools for Lambda and IAM&lt;/span&gt;
&lt;span class="c1"&gt;// Create an IAM role so Lambda can run&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambdaRole&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;assumeRolePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assumeRolePolicyForPrincipal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Attach a basic policy to the role&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RolePolicyAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambdaPolicy&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambdaRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Link to our role&lt;/span&gt;
    &lt;span class="na"&gt;policyArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWSLambdaBasicExecutionRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="c1"&gt;// Gives Lambda permission to log&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Define the Lambda function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secretFetcher&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;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nodejs18.x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Use Node.js 18&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Points to lambda/index.js’s handler function&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambdaRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Use our role’s ARN (unique ID)&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AssetArchive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="c1"&gt;// Bundle our Lambda code&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileArchive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Grab everything in the lambda folder&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;     &lt;span class="c1"&gt;// Set environment variables&lt;/span&gt;
        &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Share the Lambda’s ARN for later use&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lambda Code:&lt;/strong&gt; Made a lambda folder:&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;lambda &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;lambda &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm init &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; @pulumi/esc-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;First try at lambda/index.js:&lt;/strong&gt;&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EscClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pulumi/esc-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Import the ESC library (first version I tried)&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="c1"&gt;// Lambda’s main function&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;EscClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Connect to ESC with token&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Try to fetch the secret&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Secret fetched:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myApiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Log the secret&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Secret retrieved!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Sends a successful response&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;Deployed:&lt;/strong&gt; Ran &lt;code&gt;pulumi up&lt;/code&gt;—it showed "+ 4 to create" and set up my stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Went Wrong:&lt;/strong&gt; Read the "Challenges I Faced" section below&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Don't panic when things break—logs and patience will save you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 3: Winning and Sharing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I Did:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Final Deploy:&lt;/strong&gt; After fixing bugs, ran &lt;code&gt;pulumi up&lt;/code&gt; again to update the Lambda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; Went to AWS Console &amp;gt; Lambda &amp;gt; secretFetcher &amp;gt; Test, created a New1 event ({}), and ran it. Checked CloudWatch logs—bam, "Secret fetched: fake-api-key-xyz123"!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; Pushed everything:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Finished Lambda with ESC"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What Went Wrong:&lt;/strong&gt; Just making sure the token worked after moving it to config—easy check with &lt;code&gt;pulumi config get pulumiAccessToken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Seeing it work feels amazing, and sharing it makes it even better!&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges I Faced
&lt;/h3&gt;

&lt;p&gt;Here's where I stumbled—and how I got back up. If you hit these, you'll know what to do!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;"Cannot find name 'pulumi'" in index.ts&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; TypeScript didn't recognize pulumi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Added &lt;code&gt;import * as pulumi from "@pulumi/pulumi";&lt;/code&gt; at the top. Simple miss!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;"Type 'string | undefined'" for PULUMI_ACCESS_TOKEN&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; TypeScript complained about my env var.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Wrapped it: &lt;code&gt;pulumi.output(process.env.PULUMI_ACCESS_TOKEN).apply(t =&amp;gt; t || "")&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lambda Not in AWS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Deployed, but couldn't find secretFetcher in us-east-1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; My AWS CLI region didn't match Pulumi's—set it in Pulumi.dev.yaml or CLI.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;"EscClient is not a constructor"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Lambda failed with this error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; The SDK changed—updated to &lt;code&gt;const { EscApi } = require("@pulumi/esc-sdk/esc")&lt;/code&gt;. Checked node_modules/@pulumi/esc-sdk/esc/index.js to confirm.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;"401 Unauthorized" Errors&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Lambda kept failing—logs showed "undefined pul-..." in the Authorization header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Added Configuration:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EscApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pulumi/esc-sdk/esc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Import both classes&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&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;Configuration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Set up token properly&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;EscApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Create client with config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Also switched to &lt;code&gt;openAndReadEnvironment("Emidowojo", "pulumi-secret-demo", "my-secrets")&lt;/code&gt;. // Fetch secret&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub Push Blocked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; Hardcoded token in index.ts—GitHub refused to. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Moved it to config:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;pulumiAccessToken pul-&lt;span class="k"&gt;**************************************************************&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Updated index.ts:
&lt;/li&gt;
&lt;/ul&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Use Pulumi’s config system&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pulumiAccessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pulumiAccessToken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Get token securely&lt;/span&gt;
   &lt;span class="nl"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pulumiAccessToken&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;ul&gt;
&lt;li&gt;Amended commit: &lt;code&gt;git commit --amend --no-edit &amp;amp;&amp;amp; git push --force&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using Pulumi ESC
&lt;/h2&gt;

&lt;p&gt;Pulumi made this project so much easier—here's how it helped me (and can help you too!):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What It Did:&lt;/strong&gt; I used Pulumi to write index.ts, setting up my Lambda and IAM role in TypeScript. It's like coding your cloud instead of clicking around!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;new aws.lambda.Function("secretFetcher", {...})&lt;/code&gt; built my Lambda in one go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How It Worked:&lt;/strong&gt; Ran &lt;code&gt;pulumi up&lt;/code&gt;—it showed me what it'd create (4 resources) and deployed them fast. Later updates were just ~ 1 to update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why I Loved It:&lt;/strong&gt; No confusing YAML—TypeScript felt natural. Plus, &lt;code&gt;pulumi up&lt;/code&gt; previews caught mistakes before they happened (like missing imports).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pulumi ESC kept my secrets safe! —here's the rundown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What It Did:&lt;/strong&gt; I stored &lt;code&gt;myApiKey: fake-api-key-xyz123&lt;/code&gt; in an ESC environment called &lt;code&gt;Emidowojo/pulumi-secret-demo/my-secrets&lt;/code&gt;. My Lambda grabs it securely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup:&lt;/strong&gt; Added it in app.pulumi.com under ESC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; Used &lt;code&gt;openAndReadEnvironment&lt;/code&gt; to fetch it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How I Set It Up:&lt;/strong&gt; Kept my ESC token safe with:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;pulumiAccessToken &amp;lt;your-token&amp;gt; &lt;span class="nt"&gt;--secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Passed it to Lambda in index.ts's environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why It's Great:&lt;/strong&gt; No secrets in my code—ESC encrypts them and ties right into Pulumi. Perfect for a static site needing secure API keys without the hassle of AWS Secrets Manager.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;And there you have it—&lt;em&gt;Shhh, It’s a Secret!&lt;/em&gt; is live! We’ve built an AWS Lambda that securely fetches a secret from Pulumi ESC, ready to support a static site without ever risking sensitive data in the code. From setting up Pulumi and ESC to wrestling bugs and pushing to GitHub, this journey taught me how powerful (and fun) infrastructure-as-code can be. Now that it’s working, I’m hooked on exploring more—maybe pairing this Lambda with an S3 static site next! Want to try it? Grab the repo, run &lt;code&gt;pulumi up&lt;/code&gt;, and see the magic for yourself. Happy coding!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pulumichallenge</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>From Classroom to Command Line: Building My First Live Server with AWS EC2 and NGINX</title>
      <dc:creator>Emidowojo</dc:creator>
      <pubDate>Thu, 30 Jan 2025 21:39:43 +0000</pubDate>
      <link>https://dev.to/techondiapers/from-classroom-to-command-line-building-my-first-live-server-with-aws-ec2-and-nginx-3200</link>
      <guid>https://dev.to/techondiapers/from-classroom-to-command-line-building-my-first-live-server-with-aws-ec2-and-nginx-3200</guid>
      <description>&lt;p&gt;&lt;em&gt;Five years after my first attempt, I rejoined the HNG internship program—this time choosing the &lt;a href="https://hng.tech/hire/devops-engineers" rel="noopener noreferrer"&gt;DevOps track&lt;/a&gt;. While I had explored DevOps through LinkedIn Learning courses, I remained trapped in tutorial hell, never quite taking the leap into practical application. The HNG internship presented the perfect opportunity to change that.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My journey began on a Tuesday with the first task landing in my inbox. Despite feeling daunted and exhausted from my day job, I was determined to succeed. After some initial research, I tackled the project head-on the next morning, temporarily setting aside my work responsibilities.&lt;/p&gt;

&lt;p&gt;What I thought would be a quick task turned into an adventure through multiple cloud platforms. I started with &lt;strong&gt;Digital Ocean&lt;/strong&gt;, where I successfully connected to SSH and installed &lt;strong&gt;NGINX&lt;/strong&gt;. However, I hit a wall when trying to edit the HTML file—a mysterious password requirement that no combination of credentials could satisfy.&lt;/p&gt;

&lt;p&gt;Frustrated but undeterred, I pivoted to &lt;strong&gt;&lt;a href="https://hng.tech/hire/aws-solutions-architects" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;&lt;/strong&gt;. While the experience was somewhat better, I still faced permission issues when trying to edit the HTML page. A brief attempt with Azure proved equally challenging. Through all this, I juggled work responsibilities and an empty stomach, but my determination never wavered.&lt;/p&gt;

&lt;p&gt;After nearly giving up—and reminding myself that quitting wasn't in my nature—I made one final attempt with AWS. Success finally came the following day around 3 PM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Winning Process
&lt;/h2&gt;

&lt;p&gt;Here's how I ultimately succeeded:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Created an AWS EC2 Instance (Ubuntu Server 22.04 LTS) using the free tier t3.micro&lt;/li&gt;
&lt;li&gt;Generated a key pair named &lt;strong&gt;DevOpsKey&lt;/strong&gt; (.pem file)&lt;/li&gt;
&lt;li&gt;Configured the security group to allow SSH (Port 22) from my IP and HTTP (Port 80) from anywhere&lt;/li&gt;
&lt;li&gt;Connected to the EC2 instance via SSH using:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Desktop/projects
&lt;span class="nb"&gt;chmod &lt;/span&gt;400 DevOpsKey.pem
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"DevOpsKey.pem"&lt;/span&gt; ubuntu@16.171.194.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Installed and configured NGINX:&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;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;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start nginx
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Created 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 shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /var/www/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Added my custom HTML:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;DevOps Stage 0&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to DevOps Stage 0 - [Emidowojo Opaluwa]/[Emidowojo]&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving the file (Ctrl + O, Enter, Ctrl + X), I visited &lt;a href="http://16.171.194.50" rel="noopener noreferrer"&gt;http://16.171.194.50&lt;/a&gt; and saw my custom message displayed proudly in the browser. Below is a screenshot of my custom message&lt;/p&gt;

&lt;p&gt;While the journey was frustrating at times, it was incredibly rewarding. The hands-on experience taught me more than any tutorial could have. Here's to the remaining nine stages of this adventure! ✨&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%2Fqwswlc4x5vuhmo3e03zy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqwswlc4x5vuhmo3e03zy.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Debugging Authorization: How Cerbos Makes Troubleshooting Access Issues a Breeze</title>
      <dc:creator>Emidowojo</dc:creator>
      <pubDate>Wed, 15 Jan 2025 10:46:13 +0000</pubDate>
      <link>https://dev.to/techondiapers/debugging-authorization-how-cerbos-makes-troubleshooting-access-issues-a-breeze-1955</link>
      <guid>https://dev.to/techondiapers/debugging-authorization-how-cerbos-makes-troubleshooting-access-issues-a-breeze-1955</guid>
      <description>&lt;p&gt;&lt;em&gt;When you hear the word "authorization," what comes to your mind? Before I learned about it properly, I thought it was just about checking if someone was allowed to do something - like a simple yes or no gate. While that basic idea isn't wrong, there's so much more beneath the surface. Authorization is really about creating an intricate web of trust and permissions that determines not just who can access what, but how different parts of a system interact with each other.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And that's where tools like &lt;strong&gt;&lt;a href="https://www.cerbos.dev/" rel="noopener noreferrer"&gt;Cerbos&lt;/a&gt;&lt;/strong&gt; come into play. Just like how we evolved from using simple keys to sophisticated access control systems, we have moved beyond basic allow/deny rules to powerful &lt;strong&gt;&lt;a href="https://www.cerbos.dev/features-benefits-and-use-cases/pbac" rel="noopener noreferrer"&gt;policy-based authorization&lt;/a&gt;&lt;/strong&gt;. Cerbos lets you define these complex permission rules in a way that's both powerful and elegant - think of it as the master conductor orchestrating who gets to do what across your entire system, making sure every access request follows the exact rules you've set up, without missing a beat.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;To get the most out of this article, readers should have a foundational understanding of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Authorization Concepts&lt;/strong&gt;: Authentication vs. authorization, roles, permissions, and access control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IAM Systems&lt;/strong&gt;: Role-based or attribute-based access control models.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;YAML Configuration&lt;/strong&gt;: Syntax and editing basics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distributed Systems&lt;/strong&gt;: Managing access in microservices/cloud setups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cerbos Basics&lt;/strong&gt;: Overview of Cerbos and its role in simplifying authorization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging Tools&lt;/strong&gt;: Experience with logs and troubleshooting.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Introduction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understanding Common Authorization Debugging Challenges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cerbos Debugging Tools and Features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step-by-Step Debugging with Cerbos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Benefits of Using Cerbos for Debugging&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conclusion&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fkgx79jx0j3keqz9htptu.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%2Fkgx79jx0j3keqz9htptu.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Debugging authorization can feel like solving a puzzle with missing pieces, especially when users are denied access despite having the right permissions. In this article, we explore common authorization issues and how Cerbos simplifies debugging access problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pain of Debugging Authorization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Misconfigured roles/permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lack of visibility into access decisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complexities in distributed systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional debugging approaches often rely on guesswork, leading to time-consuming and error-prone processes. Cerbos, on the other hand, offers clear insights and simplifies access troubleshooting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Cerbos Simplifies the Process&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enter Cerbos—a policy-based authorization tool that’s designed to make debugging access issues not just easier, but almost enjoyable. With Cerbos, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clear Audit Logs&lt;/strong&gt;: Understand every access decision at a glance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Readable Policies&lt;/strong&gt;: No more struggling with cryptic configuration files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Policy Testing Tools&lt;/strong&gt;: Validate changes before they go live, saving you from future headaches.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the sections ahead, we’ll break down how Cerbos addresses the pain points of traditional debugging and gives you the tools to resolve access issues efficiently and with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Common Authorization Debugging Challenges
&lt;/h2&gt;

&lt;p&gt;When it comes to debugging authorization, things can get tricky fast. It’s not just about getting a simple "yes" or "no" on access; there are layers of complexity. Let’s explore some of the most common challenges developers face when debugging authorization issues.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Misconfigured Roles and Permissions&lt;/strong&gt;&lt;br&gt;
Permissions may not align as expected, leading to access denials despite correct roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lack of Transparency&lt;/strong&gt;&lt;br&gt;
Without clear feedback on access decisions, troubleshooting can become inefficient.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distributed Systems&lt;/strong&gt;&lt;br&gt;
Debugging becomes more complicated in environments where services and policies are spread across multiple systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By understanding these common challenges, you can start thinking about ways to make your authorization debugging process smoother, more efficient, and way less frustrating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cerbos Debugging Tools and Features&lt;/strong&gt;&lt;br&gt;
When it comes to debugging authorization issues, Cerbos is like having a clear roadmap in the middle of a foggy forest. Here’s how its tools make the journey a lot easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit Logs: Gaining Visibility into Access Decisions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of audit logs as your application’s diary. They track every access decision, giving you a full breakdown of who accessed what and why. Instead of guessing where things went wrong, you can dive into detailed logs and pinpoint exactly where a misstep occurred. No more blind troubleshooting—Cerbos makes it transparent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human-Readable Policies: Identifying Misconfigurations Easily&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration files can often feel like a secret code, but Cerbos’ human-readable policies cut through the complexity. Instead of struggling with cryptic rules, you can quickly scan through policies in plain language, making it easier to spot any misconfigurations. It’s like reading a map instead of a maze.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testing Policies: Validating Before Deploying&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cerbos lets you test your policies before they go live. This means you can validate your rules in a controlled environment, ensuring they work as intended without breaking anything in production. It’s like running a rehearsal before the big performance—no surprises, just smooth execution.&lt;/p&gt;

&lt;p&gt;With these tools, debugging authorization issues becomes a breeze, and the process of fixing them is both faster and more accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Debugging with Cerbos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A Common Scenario: Denied Access with Correct Role&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s start with the problem at hand: a user is assigned the correct role, but they can’t access the resource they need. You check the permissions, and everything seems fine. But the system still won’t let them through. This is a classic case where debugging tools like Cerbos can save you time and sanity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Audit Logs to Trace Issues&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first thing you’ll want to do is dig into the audit logs. Cerbos provides detailed logs of every authorization decision made. By reviewing these logs, you can quickly see what happened when the user attempted to access the resource. Was it a permissions issue? Or maybe the role didn’t get applied as expected? The logs will give you the visibility you need to start tracing the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewing and Adjusting YAML Policies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next up: &lt;strong&gt;YAML policies&lt;/strong&gt;. With Cerbos, you define your access control rules in YAML, making it easy to see exactly what’s happening under the hood. If the logs point to a permissions issue, it’s time to take a closer look at the policies. Was the correct role linked to the right permissions? Sometimes, a small typo or misconfiguration can cause big headaches, so give those policies a thorough review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing and Deploying Fixes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you've tracked down the issue and adjusted the policies, it’s time to test. Cerbos lets you simulate access control decisions, so you can ensure that everything works as expected before pushing changes live. Run a few tests to confirm that the denied user can now access the resource. If everything checks out, deploy the fix to production with confidence.&lt;/p&gt;

&lt;p&gt;By following these steps, you’ll not only resolve the access issue but also build a better understanding of how Cerbos helps streamline authorization debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Using Cerbos for Debugging
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Time Savings with Clear Insights&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Cerbos, debugging authorization issues no longer feels like searching for a needle in a haystack. The clear, human-readable audit logs provide instant insights into access decisions, saving you valuable time. Instead of diving into complex logs or running endless tests, you can quickly pinpoint what went wrong and get back to business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved Accuracy with Readable and Testable Policies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cerbos doesn’t just make debugging easier—it makes your policies more reliable. Its readable policy language and built-in testing tools allow you to validate changes before they go live, ensuring you catch potential issues early. No more guessing if a policy change will work as expected—Cerbos gives you the confidence that everything is set up correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enhanced Collaboration Between Teams&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Debugging authorization often requires input from multiple teams—security, development, operations. Cerbos simplifies collaboration by making policies transparent and easy to understand. With the ability to test policies together and see who has access to what, everyone can stay on the same page and solve problems faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
In a nutshell, Cerbos makes debugging authorization a whole lot easier. With features like &lt;strong&gt;&lt;a href="https://www.cerbos.dev/features-benefits-and-use-cases/audit-logs" rel="noopener noreferrer"&gt;detailed audit logs&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://www.cerbos.dev/features-benefits-and-use-cases/human-readable-authorization" rel="noopener noreferrer"&gt;human-readable policies&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href="https://www.cerbos.dev/news/new-tools-for-effortless-policy-creation-and-testing-in-cerbos-hub" rel="noopener noreferrer"&gt;testing tools&lt;/a&gt;&lt;/strong&gt;, Cerbos helps you quickly identify and fix access issues. Gone are the days of sifting through endless lines of code or grappling with confusing configurations.&lt;/p&gt;

&lt;p&gt;If you’re tired of the headache that comes with troubleshooting access problems, it’s time to give Cerbos a try. Its simple, powerful approach can save you time, boost your confidence in your authorization setup, and make collaboration with your team smoother than ever. So, take the plunge and see how Cerbos can make your life easier—and your access control, a breeze!&lt;/p&gt;

</description>
      <category>security</category>
      <category>authorisation</category>
      <category>debugging</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
