<?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.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 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>
