<?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: Jay Watson</title>
    <description>The latest articles on DEV Community by Jay Watson (@jaywatson2pt0).</description>
    <link>https://dev.to/jaywatson2pt0</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%2F2177953%2F0bd52c95-c6ed-4af7-8275-434fb9cc7ca4.jpeg</url>
      <title>DEV Community: Jay Watson</title>
      <link>https://dev.to/jaywatson2pt0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jaywatson2pt0"/>
    <language>en</language>
    <item>
      <title>Let's Build a Full-Stack App Using the MERN Stack! Part 3: React NextJS UI</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Tue, 25 Mar 2025 23:43:55 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-3-react-nextjs-ui-5fcd</link>
      <guid>https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-3-react-nextjs-ui-5fcd</guid>
      <description>&lt;p&gt;Now that we have the database and backend set up, let's create the UI. We're using the MERN stack, where "R" stands for React. We'll be using Next.js, a React framework that extends React's capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Next.js
&lt;/h3&gt;

&lt;p&gt;To create a Next.js application, run the following command:&lt;br&gt;
&lt;code&gt;npx create-next-app@latest&lt;/code&gt;&lt;br&gt;
You'll be prompted with several configuration choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ok to proceed? → y&lt;/li&gt;
&lt;li&gt;What is your project named? → ui&lt;/li&gt;
&lt;li&gt;Would you like to use TypeScript? → Yes&lt;/li&gt;
&lt;li&gt;Would you like to use ESLint? → Yes&lt;/li&gt;
&lt;li&gt;Would you like to use Tailwind CSS? → Yes&lt;/li&gt;
&lt;li&gt;Would you like your code inside a src/ directory? → No&lt;/li&gt;
&lt;li&gt;Would you like to use App Router? → Yes&lt;/li&gt;
&lt;li&gt;Would you like to use Turbopack for next dev? → No&lt;/li&gt;
&lt;li&gt;Would you like to customize the import alias (@/* by default)? → No&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Start the Development Server
&lt;/h3&gt;

&lt;p&gt;Navigate into the ui folder and run the app:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Axios
&lt;/h3&gt;

&lt;p&gt;Next, install Axios to handle API requests:&lt;br&gt;
&lt;code&gt;npm install axios&lt;/code&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Clean Up &lt;code&gt;page.tsx&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Delete everything inside page.tsx, so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function Home() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Things&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0ik0q4w1xbdnqlp1vya.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%2Fj0ik0q4w1xbdnqlp1vya.png" alt="Things" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's set next.js to 'use client', add imports, and create our interfaces. In Next.js 13, components are server components by default. Server components are rendered on the server and sent as static HTML to the client. They are great for performance and SEO but cannot use client-side features such as react hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use client'

import { useEffect, useState } from 'react';
import axios from 'axios';

interface Thing {
  _id: string;
  title: string;
  description: string;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, add the following methods to the default Home function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [things, setThings] = useState&amp;lt;Thing[]&amp;gt;([]);
const [selectedThing, setSelectedThing] = useState&amp;lt;Thing | null&amp;gt;(null);

// Base API endpoint
const API_BASE_URL = 'http://localhost:5000/things';

useEffect(() =&amp;gt; {
  getTasks();
}, []);

// Handles radio button click and populates the form with the selected thing's data
const handleRadioClick = (thing: Thing) =&amp;gt; {
  setSelectedThing(thing);
  populateForm(thing);
};

// Populates the form fields with the data of the selected thing
const populateForm = (thing: Thing) =&amp;gt; {
  const form = document.querySelector('form');
  if (form) {
    (form.querySelector('input[name="title"]') as HTMLInputElement).value = thing.title;
    (form.querySelector('input[name="description"]') as HTMLInputElement).value = thing.description;
  }
};

// Clears the selected thing and resets the form and radio button selection
const clearSelectedThing = () =&amp;gt; {
  setSelectedThing(null);
  clearForm();
  clearRadioSelection();
};

// Clears the form fields
const clearForm = () =&amp;gt; {
  const form = document.querySelector('form');
  if (form) {
    (form.querySelector('input[name="title"]') as HTMLInputElement).value = '';
    (form.querySelector('input[name="description"]') as HTMLInputElement).value = '';
  }
};

// Clears the radio button selection
const clearRadioSelection = () =&amp;gt; {
  const radios = document.querySelectorAll('input[type="radio"]');
  radios.forEach(radio =&amp;gt; (radio as HTMLInputElement).checked = false);
};

// Fetches the list of tasks from the server
const getTasks = async () =&amp;gt; {
  try {
    const res = await axios.get(API_BASE_URL);
    setThings(res.data);
  } catch (error) {
    console.error('Error fetching tasks:', error);
  }
};

// Adds or edits a task based on the form data
const addEditTask = async (event: React.FormEvent) =&amp;gt; {
  event.preventDefault();
  const form = event.target as HTMLFormElement;
  const formData = new FormData(form);
  const title = formData.get('title') as string;
  const description = formData.get('description') as string;

  try {
    if (selectedThing) {
      const res = await axios.put(`${API_BASE_URL}/${selectedThing._id}`, { title, description });
      setThings(things.map(thing =&amp;gt; (thing._id === selectedThing._id ? res.data : thing)));
    } else {
      const res = await axios.post(API_BASE_URL, { title, description });
      setThings([...things, res.data]);
    }
    clearSelectedThing();
  } catch (error) {
    console.error('Error adding/editing task:', error);
  }
};

// Deletes the selected task
const deleteSelectedThing = async () =&amp;gt; {
  if (selectedThing) {
    try {
      await axios.delete(`${API_BASE_URL}/${selectedThing._id}`);
      setThings(things.filter(thing =&amp;gt; thing._id !== selectedThing._id));
      clearSelectedThing();
    } catch (error) {
      console.error('Error deleting task:', error);
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, replace all the html being returned with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div&amp;gt;
  &amp;lt;h1&amp;gt;Things&amp;lt;/h1&amp;gt;

  &amp;lt;h2&amp;gt;All Tasks&amp;lt;/h2&amp;gt;
  &amp;lt;ul&amp;gt;
    {things.map((thing) =&amp;gt; (
      &amp;lt;li key={thing._id}&amp;gt;
        &amp;lt;input
          type="radio"
          id={thing._id}
          name="thing"
          onClick={() =&amp;gt; handleRadioClick(thing)}
        /&amp;gt;
        &amp;lt;label htmlFor={thing._id}&amp;gt;
          {thing.title} - {thing.description}
        &amp;lt;/label&amp;gt;
      &amp;lt;/li&amp;gt;
    ))}
  &amp;lt;/ul&amp;gt;

  &amp;lt;h2&amp;gt;{selectedThing === null ? 'Add' : 'Edit'} Task&amp;lt;/h2&amp;gt;
  &amp;lt;form onSubmit={addEditTask}&amp;gt;
    Name: &amp;lt;input type="text" name="title" /&amp;gt;&amp;lt;br /&amp;gt;
    Description: &amp;lt;input type="text" name="description" /&amp;gt;&amp;lt;br /&amp;gt;
    &amp;lt;button type="submit"&amp;gt;{selectedThing === null ? 'Add Task' : 'Edit Task'}&amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;

  &amp;lt;button onClick={clearSelectedThing}&amp;gt;Clear Selected Thing&amp;lt;/button&amp;gt;
  &amp;lt;button onClick={deleteSelectedThing}&amp;gt;Delete Selected Thing&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bam! There you go! You now have full CRUD applications.&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%2Fwbr7b6n57l4547gjtvtv.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%2Fwbr7b6n57l4547gjtvtv.png" alt="full-stack MERN example" width="800" height="937"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>fullstack</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Let's Build a Full-Stack App Using the MERN Stack! Part 2: Node Backend</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Mon, 10 Mar 2025 10:50:50 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-2-node-backend-nid</link>
      <guid>https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-2-node-backend-nid</guid>
      <description>&lt;p&gt;Now that we have a database (see &lt;em&gt;&lt;strong&gt;&lt;a href="https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-1-mongo-db-1281"&gt;Let's Build a Full-Stack App Using the MERN Stack! Part 1: Mongo DB&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;), it's time to build the backend! In this tutorial, we’ll set up a simple backend using Node.js, Express, and MongoDB. By the end, you’ll have a working API that you can interact with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up the Backend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create the Backend Folder
&lt;/h3&gt;

&lt;p&gt;First, create a new folder for your backend and open it in your code editor:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Initialize a Node.js Project
&lt;/h3&gt;

&lt;p&gt;Run the following command to initialize a new Node.js project: &lt;code&gt;npm init -y&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Install Dependencies
&lt;/h3&gt;

&lt;p&gt;We need to install some essential packages for our backend: &lt;code&gt;npm install express mongoose cors dotenv&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;express – Handles routing and HTTP requests.&lt;/li&gt;
&lt;li&gt;mongoose – Connects and interacts with MongoDB.&lt;/li&gt;
&lt;li&gt;cors – Allows cross-origin requests (important for frontend-backend communication).&lt;/li&gt;
&lt;li&gt;dotenv – Manages environment variables like database connection strings.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 2: Creating the Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create an index.js File
&lt;/h3&gt;

&lt;p&gt;Inside the backend folder, create a new file named index.js and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const Thing = require('./models/Thing');
require("dotenv").config();

const app = express();
const PORT = process.env.PORT || 5000;

// Middleware
app.use(cors());
app.use(express.json());

// MongoDB Connection
mongoose
  .connect(process.env.MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() =&amp;gt; console.log("MongoDB Connected!"))
  .catch((err) =&amp;gt; console.log(err));

// Routes
app.get("/", (req, res) =&amp;gt; {
  res.send("API running!");
});

// Create a thing
app.post('/things', async (req, res) =&amp;gt; {
  const thing = new Thing(req.body);
  await thing.save();
  res.status(201).send(thing);
});

// Get all things
app.get('/things', async (req, res) =&amp;gt; {
  const things = await Thing.find();
  res.send(things);
});

// Update a thing
app.put('/things/:id', async (req, res) =&amp;gt; {
  const thing = await Thing.findByIdAndUpdate(req.params.id, req.body, { new: true });
  res.send(thing);
});

// Delete a thing
app.delete('/things/:id', async (req, res) =&amp;gt; {
  await Thing.findByIdAndDelete(req.params.id);
  res.status(204).send();
});

app.listen(PORT, () =&amp;gt; console.log(`Server running on port ${PORT}`));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Set Up Environment Variables
&lt;/h3&gt;

&lt;p&gt;Create a .env file in the backend folder and add your MongoDB connection string: &lt;code&gt;MONGO_URI=mongodb://watson:watson@localhost:27000/&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Creating a Model
&lt;/h2&gt;

&lt;p&gt;Inside the backend folder, create a new folder called models.&lt;/p&gt;

&lt;p&gt;Then, inside the models folder, create a new file called Thing.js and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mongoose = require("mongoose");

const ThingSchema = new mongoose.Schema(
  {
    title: { type: String, required: true },
    description: { type: String, required: false },
  },
  { timestamps: true }
);

module.exports = mongoose.model("Thing", ThingSchema);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines a simple Thing model with a &lt;em&gt;title&lt;/em&gt; and &lt;em&gt;description&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%2Fyporenawktgi3hco7yp9.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%2Fyporenawktgi3hco7yp9.png" alt="VS Code screenshot of folder structure and model" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Now, let’s start the backend server: &lt;code&gt;node index.js&lt;/code&gt;&lt;br&gt;
If everything is set up correctly, you should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MongoDB Connected!
Server running on port 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  1. Install Postman (if you haven't already)
&lt;/h3&gt;

&lt;p&gt;Download Postman and install it to test your API.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create Some Things
&lt;/h3&gt;

&lt;p&gt;Open Postman.&lt;br&gt;
Set the request type to POST.&lt;br&gt;
Enter &lt;a href="http://localhost:5000/things" rel="noopener noreferrer"&gt;http://localhost:5000/things&lt;/a&gt;.&lt;br&gt;
Go to the Body tab, select raw, and choose JSON.&lt;br&gt;
Add the following JSON data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "title": "First Thing",
  "description": "This is a test thing"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click Send to create a new Thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Get Some Things!
&lt;/h3&gt;

&lt;p&gt;Change the request type to GET.&lt;br&gt;
Enter &lt;a href="http://localhost:5000/things" rel="noopener noreferrer"&gt;http://localhost:5000/things&lt;/a&gt;.&lt;br&gt;
Click Send to retrieve all Things.&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%2F4uttz7d477kutmads2hw.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%2F4uttz7d477kutmads2hw.png" alt="Postman screenshot: getting things" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;🎉 Whoohoo! You now have a working backend service with &lt;strong&gt;Node.js, Express, MongoDB, and Mongoose&lt;/strong&gt;. This is a great foundation for building a full-stack application. In the next steps, you can extend this API with more endpoints, authentication, and validation.&lt;/p&gt;

&lt;p&gt;Let me know if you have any questions! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>express</category>
      <category>react</category>
    </item>
    <item>
      <title>Let's Build a Full-Stack App Using the MERN Stack! Part 1: Mongo DB</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Sun, 09 Mar 2025 01:22:07 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-1-mongo-db-1281</link>
      <guid>https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-1-mongo-db-1281</guid>
      <description>&lt;h2&gt;
  
  
  Where Are We Storing Stuff?
&lt;/h2&gt;

&lt;p&gt;Before we code our full-stack MERN (MongoDB, Express, React, and Node.js) application, we need a place to store our data. The best way to do this is to set up a MongoDB instance. We’ll run MongoDB inside a Docker container to keep things simple and easily manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up MongoDB with Docker
&lt;/h2&gt;

&lt;p&gt;We'll use the official MongoDB image from Docker Hub. To pull and run the MongoDB container, execute the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`docker run -d --name mongodb -p 27000:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=watson \
  -e MONGO_INITDB_ROOT_PASSWORD=watson mongo`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker run -d runs the container in detached mode (in the background).&lt;/li&gt;
&lt;li&gt;--name mongodb names the container "mongodb" for easy reference.&lt;/li&gt;
&lt;li&gt;-p 27000:27017 maps port 27000 on your local machine to MongoDB's default port 27017.&lt;/li&gt;
&lt;li&gt;-e MONGO_INITDB_ROOT_USERNAME=watson sets the MongoDB root username to "watson".&lt;/li&gt;
&lt;li&gt;-e MONGO_INITDB_ROOT_PASSWORD=watson sets the password to "watson".&lt;/li&gt;
&lt;li&gt;mongo specifies the image to use (Docker will pull it if it’s not already available locally).&lt;/li&gt;
&lt;li&gt;Once this command runs successfully, you’ll have a MongoDB instance up and running in a Docker container!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connecting to MongoDB
&lt;/h2&gt;

&lt;p&gt;Now that we have our MongoDB instance running, we need a way to interact with it. One of the best tools for this is Studio 3T for MongoDB. It provides an intuitive UI to visualize and manage your database.&lt;/p&gt;

&lt;p&gt;To connect Studio 3T to your MongoDB instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download and install Studio 3T (there’s a free tier that works great).&lt;/li&gt;
&lt;li&gt;Open the application and create a New Connection.&lt;/li&gt;
&lt;li&gt;Create a new connection&lt;/li&gt;
&lt;li&gt;Past the connection string into the connection as follows: mongodb://watson:watson@localhost:27000/
&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%2F2ieidc33l4mj230cc4db.png" alt="Connect to a Mongo DB" width="800" height="413"&gt;
&lt;/li&gt;
&lt;li&gt;Click Test Connection to ensure everything works.&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%2F7sbamwe4q3cgap4cyzg7.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%2F7sbamwe4q3cgap4cyzg7.png" alt="Test the connection" width="800" height="838"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save and connect!&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;With our database set up, we’re ready to start building the backend of our application using Express.js. In the next part of this series, we’ll:&lt;/p&gt;

&lt;p&gt;Set up a Node.js server&lt;/p&gt;

&lt;p&gt;Connect it to our MongoDB database&lt;/p&gt;

&lt;p&gt;Create our first API endpoints&lt;/p&gt;

&lt;p&gt;Check out &lt;strong&gt;&lt;em&gt;&lt;a href="https://dev.to/jaywatson2pt0/lets-build-a-full-stack-app-using-the-mern-stack-part-2-node-backend-nid"&gt;Let's Build a Full-Stack App Using the MERN Stack! Part 2: Node Backend&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mongodb</category>
      <category>database</category>
      <category>node</category>
    </item>
    <item>
      <title>Standup Serverless Jenkins on Fargate with Terraform - Part 2: ECS Deployment</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Wed, 20 Nov 2024 11:29:48 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/standup-serverless-jenkins-on-fargate-with-terraform-part-2-ecs-deployment-1mii</link>
      <guid>https://dev.to/jaywatson2pt0/standup-serverless-jenkins-on-fargate-with-terraform-part-2-ecs-deployment-1mii</guid>
      <description>&lt;p&gt;This tutorial assumes that you've completed &lt;em&gt;&lt;a href="https://dev.to/jay_watson_6587c40fd413dc/standup-serverless-jenkins-on-fargate-with-terraform-part-1-networking-ba"&gt;Standup Serverless Jenkins on Fargate with Terraform - Part 1: Networking &lt;/a&gt;&lt;/em&gt;. If not, you need to do that first. &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%2F1mqm3z4rb3q4ipi1kfkt.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%2F1mqm3z4rb3q4ipi1kfkt.png" alt="Architecture diagram" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To start, create &lt;em&gt;&lt;strong&gt;variables.tf&lt;/strong&gt;&lt;/em&gt; and add the following variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "application_name" {
  description = "Name of the application"
  type        = string
}

variable "aws_vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "jenkins_controller_identifier" {
  description = "Name of the jenkins controller"
  type        = string
}

variable "jenkins_agent_port" {
  description = "Port Jenkins agent uses to connect to controller"
  type        = number
}

variable "jenkins_controller_port" {
  description = "Port used to connect to Jenkins controller"
  type        = number
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create &lt;em&gt;&lt;strong&gt;terraform.tfvars&lt;/strong&gt;&lt;/em&gt; to give your variables values. Remember the network we created in lesson one. Assuming you made that, grab the VPC ID and set the value for aws_vpc_id. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk5yhovz9hqtdqcnhukb.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%2Fyk5yhovz9hqtdqcnhukb.png" alt="AWS VPC" width="800" height="189"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application_name = "serverless-jenkins-on-ecs"
jenkins_controller_identifier = "jenkins-controller"
jenkins_agent_port            = 50000
jenkins_controller_port       = 8080
aws_vpc_id = "vpc-ID_Get-This-From-AWS"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;em&gt;&lt;strong&gt;data.tf&lt;/strong&gt;&lt;/em&gt;, so you can get the necessary information to create your ECS resources. Note that we're grabbing the subnets that we want by filtering on VPC ID and tag names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "aws_region" "current" {}

# Current AWS account
data "aws_caller_identity" "this" {}

data "aws_subnets" "public" {
  filter {
    name   = "vpc-id"
    values = [var.aws_vpc_id]
  }
  filter {
    name   = "tag:Name"
    values = ["public-*"]
  }
}

data "aws_subnets" "private" {
  filter {
    name   = "vpc-id"
    values = [var.aws_vpc_id]
  }
  filter {
    name   = "tag:Name"
    values = ["private-*"]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;strong&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/strong&gt; to bring in the AWS providers from the HashiCorp registry. Terraform providers are simply plugins that allow you to interact with APIs. In this case, we need to work with AWS APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need storage for our Jenkins. Create &lt;strong&gt;&lt;em&gt;efs.tf&lt;/em&gt;&lt;/strong&gt; as we plan to use AWS Elastic File System (EFS). Note the comments above each snippet to understand what the code is doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Elastic File System (EFS) 
resource "aws_efs_file_system" "this" {
  creation_token   = var.application_name
  encrypted        = true
  performance_mode = "generalPurpose"
  throughput_mode  = "bursting"
}

# EFS Mount Targets
resource "aws_efs_mount_target" "this" {
  for_each = toset(data.aws_subnets.private.ids)

  file_system_id  = aws_efs_file_system.this.id
  subnet_id       = each.value
  security_groups = [aws_security_group.efs.id]
}

# EFS security group
resource "aws_security_group" "efs" {
  name   = "efs"
  vpc_id = var.aws_vpc_id
}

resource "aws_security_group_rule" "ecs_ingress" {
  security_group_id = aws_security_group.efs.id
  type                     = "ingress"
  from_port                = 2049
  to_port                  = 2049
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.ecs_service.id
}

# EFS Access Point
resource "aws_efs_access_point" "this" {
  file_system_id = aws_efs_file_system.this.id
  posix_user {
    gid = 1000
    uid = 1000
  }
  root_directory {
    path = "/home"
    creation_info {
      owner_gid   = 1000
      owner_uid   = 1000
      permissions = 755
    }
  }
}

# EFS Policy
data "aws_iam_policy_document" "this" {
  statement {
    actions = [
      "elasticfilesystem:ClientMount",
      "elasticfilesystem:ClientWrite"
    ]
    effect = "Allow"
    resources = [
      aws_efs_file_system.this.arn,
    ]

    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
    condition {
      test     = "Bool"
      variable = "aws:SecureTransport"
      values   = ["true"]
    }
  }
}

# EFS Policy Attachment
resource "aws_efs_file_system_policy" "this" {
  file_system_id = aws_efs_file_system.this.id
  policy         = data.aws_iam_policy_document.this.json
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we go. This is why you're here and this is our largest Terraform file. Create &lt;strong&gt;&lt;em&gt;ecs.tf&lt;/em&gt;&lt;/strong&gt; to create our ECS cluster, service, etc. Like before, each section of code is annotated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ECS Cluster
resource "aws_ecs_cluster" "this" {
  name = var.application_name
}

# ECS Task Definition
resource "aws_ecs_task_definition" "this" {
  family = var.application_name
  container_definitions = templatefile("${path.module}/container_definition.tftpl", {
    container_name          = var.jenkins_controller_identifier,
    container_image         = "jenkins/jenkins:2.479.1", # latest version as of Oct. 11, 24
    jenkins_controller_port = var.jenkins_controller_port
    jenkins_agent_port      = var.jenkins_agent_port
    source_volume           = "home",
    awslogs_group           = aws_cloudwatch_log_group.this.name,
    awslogs_region          = data.aws_region.current.name,
    }
  )
  network_mode             = "awsvpc"
  cpu                      = 1024
  memory                   = 2048
  execution_role_arn       = aws_iam_role.execution.arn
  task_role_arn            = aws_iam_role.task.arn
  requires_compatibilities = ["FARGATE"]
  volume {
    name = "home"
    efs_volume_configuration {
      file_system_id     = aws_efs_file_system.this.id
      transit_encryption = "ENABLED"
      authorization_config {
        access_point_id = aws_efs_access_point.this.id
        iam             = "ENABLED"
      }
    }
  }
}

# Roles and Polices 
resource "aws_iam_role" "execution" {
  name = "ecs-execution"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      },
    ]
  })
}

resource "aws_iam_role_policy_attachment" "basic_execution_role" {
  role       = aws_iam_role.execution.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role" "task" {
  name = "ecs-task"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      },
    ]
  })
}

data "aws_iam_policy_document" "efs_access" {
  statement {
    actions = [
      "elasticfilesystem:ClientMount",
      "elasticfilesystem:ClientWrite"
    ]

    resources = [
      aws_efs_file_system.this.arn
    ]
  }
}

resource "aws_iam_policy" "efs_access" {
  name   = "efs-access"
  policy = data.aws_iam_policy_document.efs_access.json
}

resource "aws_iam_role_policy_attachment" "efs_access" {
  role       = aws_iam_role.task.name
  policy_arn = aws_iam_policy.efs_access.arn
}

data "aws_iam_policy_document" "ecs_access" {
  statement {
    actions = [
      "ecs:RegisterTaskDefinition",
      "ecs:DeregisterTaskDefinition",
      "ecs:ListClusters",
      "ecs:ListTaskDefinitions",
      "ecs:DescribeContainerInstances",
      "ecs:DescribeTaskDefinition",
      "ecs:DescribeClusters",
      "ecs:ListTagsForResource"
    ]
    resources = [
      "*"
    ]
  }

  statement {
    actions = [
      "ecs:ListContainerInstances"
    ]
    resources = [
      aws_ecs_cluster.this.arn
    ]
  }

  statement {
    actions = [
      "ecs:RunTask",
      "ecs:StopTask",
      "ecs:DescribeTasks"
    ]
    resources = [
      "*"
    ]
    condition {
      test     = "ArnEquals"
      variable = "ecs:cluster"

      values = [
        aws_ecs_cluster.this.arn
      ]
    }
  }
}

resource "aws_iam_policy" "ecs_access" {
  name   = "ecs-access"
  policy = data.aws_iam_policy_document.ecs_access.json
}

resource "aws_iam_role_policy_attachment" "ecs_access" {
  role       = aws_iam_role.task.name
  policy_arn = aws_iam_policy.ecs_access.arn
}

data "aws_iam_policy_document" "iam_access" {
  statement {
    actions = [
      "iam:GetRole",
      "iam:PassRole"
    ]

    resources = [
      aws_iam_role.execution.arn,
      aws_iam_role.agent.arn
    ]
  }
}

resource "aws_iam_policy" "iam_access" {
  name   = "iam-access"
  policy = data.aws_iam_policy_document.iam_access.json
}

resource "aws_iam_role_policy_attachment" "iam_access" {
  role       = aws_iam_role.task.name
  policy_arn = aws_iam_policy.iam_access.arn
}

resource "aws_iam_role" "agent" {
  name = "ecs-agent"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      },
    ]
  })
}

resource "aws_iam_role_policy_attachment" "admin_access" {
  role       = aws_iam_role.agent.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}


# ECS Service 
resource "aws_ecs_service" "this" {
  name            = var.application_name
  launch_type     = "FARGATE"
  cluster         = aws_ecs_cluster.this.arn
  task_definition = aws_ecs_task_definition.this.arn
  desired_count   = 1

  network_configuration {
    subnets          = data.aws_subnets.private.ids
    security_groups  = [aws_security_group.ecs_service.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.this.arn
    container_name   = var.jenkins_controller_identifier
    container_port   = var.jenkins_controller_port
  }

  service_registries {
    registry_arn = aws_service_discovery_service.this.arn
    port         = var.jenkins_agent_port
  }
}

# Security Group and Rules
resource "aws_security_group" "ecs_service" {
  name   = "ecs-jenkins-controller"
  vpc_id = var.aws_vpc_id
}

resource "aws_security_group_rule" "alb_ingress" {
  security_group_id = aws_security_group.ecs_service.id

  type                     = "ingress"
  from_port                = var.jenkins_controller_port
  to_port                  = var.jenkins_controller_port
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.alb.id
}

resource "aws_security_group_rule" "service_all_egress" {
  security_group_id = aws_security_group.ecs_service.id

  type        = "egress"
  from_port   = 0
  to_port     = 65535
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "jenkins_agent_ingress" {
  security_group_id = aws_security_group.ecs_service.id

  type                     = "ingress"
  from_port                = var.jenkins_agent_port
  to_port                  = var.jenkins_agent_port
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.ecs_jenkins_agent.id
}

resource "aws_security_group" "ecs_jenkins_agent" {
  name   = "ecs-jenkins-agents"
  vpc_id = var.aws_vpc_id
}

resource "aws_security_group_rule" "agent_all_egress" {
  security_group_id = aws_security_group.ecs_jenkins_agent.id
  type              = "egress"
  from_port         = 0
  to_port           = 65535
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
}

# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "this" {
  name              = var.application_name
  retention_in_days = 30
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;em&gt;&lt;strong&gt;service-discovery.tf&lt;/strong&gt;&lt;/em&gt; next. What if we want to add a Jenkins agent? Service Discovery will provide a good way to manage everything. When we launch a new task, it will register itself with discovery. When other resources, want to reference that task, they can query Service Discovery.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Description: This file contains the terraform code to create a private DNS namespace and a service discovery service.

# Service Discovery namespace
resource "aws_service_discovery_private_dns_namespace" "this" {
  name = var.application_name
  vpc  = var.aws_vpc_id
}

# Service Discovery service
resource "aws_service_discovery_service" "this" {
  name = var.jenkins_controller_identifier

  dns_config {
    namespace_id   = aws_service_discovery_private_dns_namespace.this.id
    routing_policy = "MULTIVALUE"

    dns_records {
      ttl  = 60
      type = "A"
    }
    dns_records {
      ttl  = 60
      type = "SRV"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how are we going to access our cluster? Aren't our tasks in private subnets? Good question and yes, they are. We need an Application Load Balancer (ALB). Create &lt;strong&gt;&lt;em&gt;alb.tf&lt;/em&gt;&lt;/strong&gt; for ALB configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ALB
resource "aws_lb" "this" {
  name = var.application_name

  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = data.aws_subnets.public.ids
}

# ALB Security Group
resource "aws_security_group" "alb" {
  name   = "alb"
  vpc_id = var.aws_vpc_id
}

# Open HTTP port 80 to the world
resource "aws_security_group_rule" "http_ingress" {
  security_group_id = aws_security_group.alb.id

  type        = "ingress"
  from_port   = 80
  to_port     = 80
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
}

# Open HTTP port 8080 to allow ALB to communicate with ECS service
resource "aws_security_group_rule" "ecs_egress" {
  security_group_id        = aws_security_group.alb.id
  type                     = "egress"
  from_port                = 8080
  to_port                  = 8080
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.ecs_service.id
}


# ALB Listener
resource "aws_lb_listener" "this" {
  load_balancer_arn = aws_lb.this.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.this.arn
  }
}

# ALB Target Group
resource "aws_lb_target_group" "this" {
  name        = var.application_name
  port        = 8080
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = var.aws_vpc_id

  health_check {
    path = "/login"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, create &lt;em&gt;&lt;strong&gt;outputs.tf&lt;/strong&gt;&lt;/em&gt; and grab the CloudWatch Log Group and Jenkins URL (ALB DNS).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "ecs_cloudwatch_log_group_name" {
  description = "Name of the ECS CloudWatch Log group"
  value       = aws_cloudwatch_log_group.this.name
}

output "jenkins_url" {
  description = "URL of the Jenkins server"
  value       = "http://${aws_lb.this.dns_name}"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you see this, you're done! Grab the jenkins_url output value and paste it into a browser. &lt;/p&gt;

&lt;p&gt;Ah...you need a password. Let's go get that. &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%2Fnklrxvk0kp1aw9tg8fgj.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%2Fnklrxvk0kp1aw9tg8fgj.png" alt="cmd console after completion" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to Amazon Elastic Container Service -&amp;gt; Clusters -&amp;gt; serverless-jenkins-on-ecs -&amp;gt; Tasks -&amp;gt; your-task -&amp;gt; Logs and get the password from the logs. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqv0q9iyclkgzvlxvcyo3.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%2Fqv0q9iyclkgzvlxvcyo3.png" alt="password in logs" width="800" height="300"&gt;&lt;/a&gt;&lt;br&gt;
Now, you're in! At this point, go ahead and install your plugins, create an admin user, etc. You're set. Jenkins is ready to use. Before we conclude, let's look at what we've done. &lt;/p&gt;

&lt;p&gt;EFS&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%2F5qh7xpeyfeeczmcqvelj.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%2F5qh7xpeyfeeczmcqvelj.png" alt="EFS" width="800" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ECS Cluster&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%2Ft73weohubjus82luehqb.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%2Ft73weohubjus82luehqb.png" alt="ECS cluster" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ECS Service&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%2F1gdc8hmhetnhzni6wbji.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%2F1gdc8hmhetnhzni6wbji.png" alt="ECS Service" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ECS Tasks&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%2Feogt3wnu8evymh2cz5pt.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%2Feogt3wnu8evymh2cz5pt.png" alt="ECS Tasks" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Service Discovery&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%2F8w258z4y375j6ycdiy0a.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%2F8w258z4y375j6ycdiy0a.png" alt="Application Load Balancer" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fin. &lt;/p&gt;

&lt;p&gt;GitHub Repo: &lt;a href="https://github.com/jWatsonDev/jenkins-ecs-fargate" rel="noopener noreferrer"&gt;https://github.com/jWatsonDev/jenkins-ecs-fargate&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ecs</category>
      <category>jenkins</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Why Are People Using Laravel?</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Mon, 18 Nov 2024 11:49:25 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/why-are-people-using-laravel-33d7</link>
      <guid>https://dev.to/jaywatson2pt0/why-are-people-using-laravel-33d7</guid>
      <description>&lt;p&gt;PHP is dead. Or is it? &lt;/p&gt;

&lt;p&gt;For whatever reason, I've heard many people talk about Laravel here lately, the most popular PHP framework. I figured I'd play around with it and see the buzz. I enjoyed it!&lt;/p&gt;

&lt;p&gt;First, we need a database. We'll use docker to run a MySQL database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a docker network
docker network create localnetwork
# Run a MySQL database
docker run -d --name mysql-container  --network=localnetwork -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql:latest
# Run phpMyAdmin
docker run -d --name phpmyadmin-container --network=localnetwork -e PMA_HOST=mysql-container -e PMA_PORT=3306 -p 8080:80 phpmyadmin/phpmyadmin:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, install Composer. If you have a Mac, use homebrew.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Use Composer to generate a fresh laravel project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer create-project --prefer-dist laravel/laravel laravel-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your database credentials in the .env file. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5by9eh7uwcqrk3a2okpe.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%2F5by9eh7uwcqrk3a2okpe.png" alt="DB credentials in .env file" width="800" height="552"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crud_app
DB_USERNAME=root
DB_PASSWORD=password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Serve it up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create routes/api.php.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
use App\Http\Controllers\ThingController;

Route::get('/things', [ThingController::class, 'index']);
Route::get('/things/{id}', [ThingController::class, 'show']);
Route::post('/things', [ThingController::class, 'store']);
Route::put('/things/{id}', [ThingController::class, 'update']);
Route::delete('/things/{id}', [ThingController::class, 'destroy']);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update app.php so that your routes are defined. &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%2Fu8p997gyu4v1s4b5qdng.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%2Fu8p997gyu4v1s4b5qdng.png" alt="make laravel route accessible" width="800" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, have Laravel generate your controller: &lt;code&gt;php artisan make:controller ThingController&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Generate your model: &lt;code&gt;php artisan make:model Thing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Update your model with &lt;code&gt;protected $guarded = [];&lt;/code&gt; This tells the database to accept everything except what you specify in the array. &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%2Fmg215gxcfyr1vr7a94ek.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%2Fmg215gxcfyr1vr7a94ek.png" alt="Laravel guarded" width="798" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Define your CRUD operations in your ThingController.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
namespace App\Http\Controllers;

use App\Models\Thing;
use Illuminate\Http\Request;

class ThingController extends Controller
{
    public function index()
    {
        $things = Thing::all();
        return response()-&amp;gt;json($things);
    }

    public function show($id)
    {
        $thing = Thing::find($id);
        return response()-&amp;gt;json($thing);
    }

    public function store(Request $request)
    {
        $thing = Thing::create($request-&amp;gt;all());
        return response()-&amp;gt;json($thing, 201);
    }

    public function update(Request $request, $id)
    {
        $thing = Thing::find($id);
        $thing-&amp;gt;update($request-&amp;gt;all());
        return response()-&amp;gt;json($thing, 200);
    }

    public function destroy($id)
    {
        Thing::destroy($id);
        return response()-&amp;gt;json(null, 204);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;php artisan migrate&lt;/code&gt; to setup the database. Say 'Yes'.&lt;/p&gt;

&lt;p&gt;Next login to phpMyAdmin and create the things table.  &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%2Ftyzvx8s7r4kmwi8ysh2g.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%2Ftyzvx8s7r4kmwi8ysh2g.png" alt="phpMyAdmin" width="800" height="754"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujhlum9ow9f5tit7fsx9.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%2Fujhlum9ow9f5tit7fsx9.png" alt="Table Creation" width="800" height="212"&gt;&lt;/a&gt;&lt;br&gt;
Note that we set the id to auto-increment. &lt;/p&gt;

&lt;p&gt;Now use postman to create some 'things'.&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%2Fji1yi4jxlv4fi1g1i5fj.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%2Fji1yi4jxlv4fi1g1i5fj.png" alt="Postman create items" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's get the 'things'.&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%2Fh05z75j1ux9bxwxduqnp.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%2Fh05z75j1ux9bxwxduqnp.png" alt="Postman get" width="800" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub repo: &lt;a href="https://github.com/jWatsonDev/sample-laravel-crud" rel="noopener noreferrer"&gt;https://github.com/jWatsonDev/sample-laravel-crud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fin. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Standup Serverless Jenkins on Fargate with Terraform - Part 1: Networking</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Mon, 04 Nov 2024 11:52:59 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/standup-serverless-jenkins-on-fargate-with-terraform-part-1-networking-ba</link>
      <guid>https://dev.to/jaywatson2pt0/standup-serverless-jenkins-on-fargate-with-terraform-part-1-networking-ba</guid>
      <description>&lt;p&gt;We need to set up our network before we get into ECS and Jenkins. We'll stamp out a VPC, public subnets, private subnets, an Internet Gateway, NAT Gateways, and route tables. Let's get started!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lzvj1968tsciova0aib.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%2F7lzvj1968tsciova0aib.png" alt="Simple AWS network stamp out" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
First, create &lt;em&gt;&lt;strong&gt;variables.tf&lt;/strong&gt;&lt;/em&gt;. This file allows us to define the variables we need—a VPC CIDR block, private subnets, and public subnets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "vpc_cidr_block" {
  description = "CIDR of vpc"
  type        = string
}

variable "public_subnets" {
  description = "Map of public subnets that should be created"
  type = map(object({
    cidr_block        = string
    availability_zone = string
  }))
}

variable "private_subnets" {
  description = "Map of private subnets that should be created"
  type = map(object({
    cidr_block        = string
    availability_zone = string
  }))
}

variable "application_name" {
  description = "Name of the application"
  type        = string
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll provide the variable definitions in &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&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;vpc_cidr_block = "10.0.0.0/24"

public_subnets = {
  subnet_1 = {
    cidr_block        = "10.0.0.0/26"
    availability_zone = "us-east-1a"
  }
  subnet_2 = {
    cidr_block        = "10.0.0.64/26"
    availability_zone = "us-east-1b"
  }
}

private_subnets = {
  subnet_1 = {
    cidr_block        = "10.0.0.128/26"
    availability_zone = "us-east-1a"
  }
  subnet_2 = {
    cidr_block        = "10.0.0.192/26"
    availability_zone = "us-east-1b"
  }
}

application_name = "serverless-jenkins-on-ecs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create &lt;em&gt;&lt;strong&gt;vpc.tf&lt;/strong&gt;&lt;/em&gt; and create the VPC first. An AWS VPC (Virtual Private Cloud) is a virtual network logically isolated from other virtual networks in the AWS Cloud, providing you with control over IP addresses, subnets, route tables, and network gateways.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# VPC
resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_hostnames = true
  tags = {
    Name = var.application_name
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the public subnets. What makes these subnets public is that they will have a route to an Internet Gateway (IGW), which allows resources within the subnet to communicate directly with the Internet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Public Subnets
resource "aws_subnet" "public" {
  for_each = var.public_subnets
  vpc_id = aws_vpc.this.id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.availability_zone
  map_public_ip_on_launch = true
  tags = {
    Name = format("public-%s-%s", var.application_name, each.value.availability_zone)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the IGW and associate it with the VPC. We'll reference it later in our route table that we'll connect to our public subnets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# IGW 
resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a route table for your public subnet and add a route to funnel traffic through the IGW. After which, create the route table association to wire up the IGW with the public subnets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.this.id
  tags = {
    Name = "public"
  }
}
# Add IGW Route 
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.this.id
}
# Associate Route Table with Subnet 
resource "aws_route_table_association" "public" {
  for_each = aws_subnet.public

  subnet_id      = each.value.id
  route_table_id = aws_route_table.public.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create the private subnets. As the name implies, these subnets are inaccessible from the outside world.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Private Subnets
resource "aws_subnet" "private" {
  for_each = var.private_subnets

  vpc_id = aws_vpc.this.id

  cidr_block        = each.value.cidr_block
  availability_zone = each.value.availability_zone

  tags = {
    Name = format("private-%s-%s", var.application_name, each.value.availability_zone)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create an Elastic IP (EIP) and a NAT Gateway. We need the EIP, as AWS requires one as part of the NAT Gateway creation. The NAT Gateway is necessary as that's how the private subnets can communicate with the Internet (egress only). Imagine that you have RHEL EC2s that need to receive yum updates. You'll need a NAT Gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# EIP for NAT Gateway
resource "aws_eip" "this" {
  for_each = aws_subnet.private
}

# NAT Gateway 
resource "aws_nat_gateway" "this" {
  for_each = aws_subnet.private

  subnet_id     = aws_subnet.public[each.key].id
  allocation_id = aws_eip.this[each.key].id

  tags = {
    Name = format("private-%s-%s", var.application_name, each.value.availability_zone)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll create the route table, the route to associate the NAT Gateway to the private subnets, and the route table association.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Private Route Table 
resource "aws_route_table" "private" {
  for_each = aws_subnet.private

  vpc_id = aws_vpc.this.id

  tags = {
    Name = format("private-%s-%s", var.application_name, each.value.availability_zone)
  }
}
# Add Route - Private Subnets to NAT Gateway
resource "aws_route" "private" {
  for_each = aws_subnet.private

  route_table_id         = aws_route_table.private[each.key].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.this[each.key].id
}
# Associate Private RT with Private Subnets
resource "aws_route_table_association" "private" {
  for_each = aws_subnet.private

  subnet_id      = each.value.id
  route_table_id = aws_route_table.private[each.key].id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are ready. Run the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize Terraform
terraform init
# Check and see what will be created
terraform plan
# Let's do this!
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, navigate to the AWS console and search for VPC. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5120u22bcwxaqo1oj236.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%2F5120u22bcwxaqo1oj236.png" alt="AWS VPC" width="800" height="185"&gt;&lt;/a&gt;&lt;br&gt;
Scroll down to the VPC resource map. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5hxlggq8291beuesgau.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%2Ft5hxlggq8291beuesgau.png" alt="VPC resource map" width="800" height="266"&gt;&lt;/a&gt;&lt;br&gt;
Click on subnets next.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsfxaf3bh6vhamlim8omp.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%2Fsfxaf3bh6vhamlim8omp.png" alt="AWS Subnets" width="800" height="273"&gt;&lt;/a&gt;&lt;br&gt;
Also, check out your Route Tables, Internet Gateways, NAT Gateways, and Elastic IPs. &lt;/p&gt;

&lt;p&gt;Fin 👏 &lt;/p&gt;

&lt;p&gt;If you need it, here's the &lt;a href="https://github.com/jWatsonDev/aws-network-stamp-out" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>aws</category>
      <category>networking</category>
      <category>jenkins</category>
      <category>fargate</category>
    </item>
    <item>
      <title>Serverless Jenkins: ECS on Fargate - Simple Setup</title>
      <dc:creator>Jay Watson</dc:creator>
      <pubDate>Wed, 16 Oct 2024 09:59:10 +0000</pubDate>
      <link>https://dev.to/jaywatson2pt0/serverless-jenkins-ecs-on-fargate-simple-setup-48bi</link>
      <guid>https://dev.to/jaywatson2pt0/serverless-jenkins-ecs-on-fargate-simple-setup-48bi</guid>
      <description>&lt;p&gt;Are you ready to deploy serverless Jenkins to ECS on Fargate? &lt;/p&gt;

&lt;p&gt;Before we get started, let's define some terms. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serverless - You aren't managing the infrastructure. AWS is.&lt;/li&gt;
&lt;li&gt;ECS - AWS container orchestration platform allowing you to deploy and manage containerized applications. &lt;/li&gt;
&lt;li&gt;Fargate - Serverless compute engine (see above). You can run ECS on EC2s (AWS VMs) or Fargate. Choose serverless. &lt;/li&gt;
&lt;li&gt;ECS Cluster - A logical group of tasks/services. You'll create this first when hosting an app in ECS. &lt;/li&gt;
&lt;li&gt;ECS Task Definition - A blueprint for your application telling ECS how to run your container. &lt;/li&gt;
&lt;li&gt;ECS Task - The instantiation of a task definition, meaning that you're running the container now. &lt;/li&gt;
&lt;li&gt;ECS Service - Runs/maintains a specified number of task definitions simultaneously. &lt;/li&gt;
&lt;li&gt;Container - A unit of software that packages needed code/dependencies for an app to run. &lt;/li&gt;
&lt;li&gt;Image - Standalone file used to create the container. &lt;/li&gt;
&lt;li&gt;Docker - The most popular container service provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, we're ready to get started. Let's do this thing! &lt;/p&gt;

&lt;p&gt;First, create the ECS cluster. Use default settings. Don't overcomplicate things. Make sure &lt;em&gt;AWS Fargate&lt;/em&gt; (serverless) is selected. &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%2Fzq0vscz2bdbgpyzvecp0.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%2Fzq0vscz2bdbgpyzvecp0.png" alt="Screenshot of ECS cluster creation" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, let's find our container image and test it locally (you'll need docker installed!). Go to dockerhub.com and search for Jenkins. Then, click on &lt;em&gt;Jenkins/Jenkins&lt;/em&gt; and copy the command.&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%2F8lzm52rfkv51be3rx9t0.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%2F8lzm52rfkv51be3rx9t0.png" alt="Screenshot of Jenkins in DockerHub" width="540" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;docker pull jenkins/jenkins&lt;/code&gt; to pull the image to your local machine. &lt;/p&gt;

&lt;p&gt;To test it locally, create a directory for the Jenkins volume and then run the image.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir jenkins_volume&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run -p 8080:8080 -p 50000:50000 -v jenkins_volume:/var/jenkins_home jenkins/Jenkins&lt;/code&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%2Fkrwujygv2stg4q50ce0k.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%2Fkrwujygv2stg4q50ce0k.png" alt="Jenkins Docker terminal" width="540" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grab the password from the terminal, open your browser, and visit localhost:8080. Put in the password and sweeeeeet! It works...hopefully. Now, go ahead and close it down (&lt;code&gt;ctrl + c&lt;/code&gt;). That was to test things out.&lt;/p&gt;

&lt;p&gt;Third, we need to create a task definition. Specify &lt;em&gt;jenkins/jenkins&lt;/em&gt; as the image URL. ECS will know to pull it from DockerHub. Use 8080 for the container port. &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%2Ft8a6500k3s6494msktz5.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%2Ft8a6500k3s6494msktz5.png" alt="Task defintion" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhc7loc7pymj1tawgyzz.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%2Fkhc7loc7pymj1tawgyzz.png" alt="Task definition screenshot 2" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;em&gt;Create&lt;/em&gt; and you're gtg. &lt;/p&gt;

&lt;p&gt;Fourth, go to your cluster and create a service. Use mostly default settings. &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%2Fqjyg8vyzq2g7wi26uwfx.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%2Fqjyg8vyzq2g7wi26uwfx.png" alt="Screenshot of cluster service creation" width="666" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1barwg1hq91iruzx7go.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%2Fm1barwg1hq91iruzx7go.png" alt="Screenshot of service creation" width="800" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do specify that you want an Application Load Balancer (ALB). We'll just listen on port 80 for now. Of course, that should be 443. &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%2F3tsiscqurhan3kbep05b.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%2F3tsiscqurhan3kbep05b.png" alt="Service creation - ALB" width="800" height="813"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;em&gt;Create&lt;/em&gt;. Now, we wait. You can follow the progress by clicking &lt;em&gt;View in CloudFormation&lt;/em&gt;, but who's that patient? &lt;/p&gt;

&lt;p&gt;Get the password from the ECS logs, then go to EC2 -&amp;gt; Load Balancers -&amp;gt; your jenkins fargate load balancer to grab the URL. You'll put the password in just like you did when you ran docker locally.  &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%2Fkoc7yu1ud2licjkhqp3s.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%2Fkoc7yu1ud2licjkhqp3s.png" alt="Screenshot of logs" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe38rao9zcj0oec835m8r.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%2Fe38rao9zcj0oec835m8r.png" alt="Screenshot of load balancer" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finished...whoops...not yet. We need to edit the ALB Target Group health check. &lt;/p&gt;

&lt;p&gt;From the ALB, click on your ECS Fargate Target Group.&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%2Ft8ct1scdsy82vuzoe8fm.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%2Ft8ct1scdsy82vuzoe8fm.png" alt="ALB target group" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Edit your Target Group health check configuration to use port 8080 and path /login?from=%2F&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%2Fh1k8yg77px81obhdej91.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%2Fh1k8yg77px81obhdej91.png" alt="Target Group health check configuration" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you're finished!! 🎉🎉🎉&lt;/p&gt;

</description>
      <category>fargate</category>
      <category>aws</category>
      <category>ecs</category>
      <category>jenkins</category>
    </item>
  </channel>
</rss>
