Building a To-Do App with Node.js, Docker, and Kubernetes
Building a To-Do App with Node.js, Docker, and Kubernetes
Introduction
This tutorial will guide you through building a simple To-Do List application using Node.js and Express, containerizing it with Docker, and deploying it on Kubernetes. You'll learn the fundamentals of RESTful APIs, containerization, and orchestration in a modern development setup.
Table of Contents
- Chapter 1: Setting up the Node.js Application
- Chapter 2: Adding MongoDB for Data Persistence
- Chapter 3: Dockerizing the Application
- Chapter 4: Running the Application with Docker Compose
- Chapter 5: Deploying the Application on Kubernetes
- Chapter 6: Accessing and Debugging the Application
- Conclusion
Let's start building!
Chapter 1: Setting up the Node.js Application
In this chapter, you'll set up a basic Node.js and Express application with a simple REST API for managing to-do items.
Step 1: Initialize the Project
Start by creating a new directory and initializing a Node.js project.
mkdir todo-app
cd todo-app
npm init -y
This command will generate a package.json
file that stores metadata about the project and its dependencies.
Step 2: Install Dependencies
Install Express for building the API and Mongoose for interacting with MongoDB.
npm install express mongoose
Step 3: Create the Application Files
- index.js: The main file to start the server.
- config/dbConfig.js: Configuration for MongoDB.
- models/todoModel.js: MongoDB schema and model.
- controllers/todoController.js: Contains logic for handling API requests.
- routes/todoRoutes.js: Manages the application's routes.
Step 4: Define MongoDB Configuration
Create a config
folder and add a dbConfig.js
file.
// config/dbConfig.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected successfully');
} catch (error) {
console.error('MongoDB connection failed:', error.message);
process.exit(1);
}
};
module.exports = connectDB;
This function connects to MongoDB, using the URI provided in the .env
file.
Step 5: Define the To-Do Schema
Create a models
folder and add a todoModel.js
file for the MongoDB schema.
// models/todoModel.js
const mongoose = require('mongoose');
const todoSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
completed: {
type: Boolean,
default: false,
},
createdAt: {
type: Date,
default: Date.now,
},
});
const Todo = mongoose.model('Todo', todoSchema);
module.exports = Todo;
This schema defines the structure of a to-do item with three fields: title
, completed
, and createdAt
.
Step 6: Set Up the API Controller
Create a controllers
folder and add a todoController.js
file.
// controllers/todoController.js
const Todo = require('../models/todoModel');
const getTodos = async (req, res, next) => {
try {
const todos = await Todo.find();
res.json(todos);
} catch (error) {
next(error);
}
};
const createTodo = async (req, res, next) => {
try {
const { title } = req.body;
const newTodo = new Todo({
title,
});
const savedTodo = await newTodo.save();
res.status(201).json(savedTodo);
} catch (error) {
next(error);
}
};
module.exports = {
getTodos,
createTodo,
};
Step 7: Define the Routes
Create a routes
folder and add a todoRoutes.js
file.
// routes/todoRoutes.js
const express = require('express');
const { getTodos, createTodo } = require('../controllers/todoController');
const router = express.Router();
router.get('/todos', getTodos);
router.post('/todos', createTodo);
module.exports = router;
This file defines the /todos
endpoint with GET
and POST
methods to retrieve and add to-do items.
Step 8: Connect Everything in index.js
In index.js
, bring everything together.
const express = require('express');
const connectDB = require('./config/dbConfig');
const todoRoutes = require('./routes/todoRoutes');
const app = express();
const PORT = process.env.PORT || 3000;
connectDB();
app.use(express.json());
app.use('/api', todoRoutes);
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Step 9: Configure Environment Variables
Create a .env
file in the root directory with the following:
MONGO_URI=your_mongo_connection_string
PORT=3000
Replace your_mongo_connection_string
with the MongoDB URI.
Step 10: Run the Application
Start the application with the following command:
node index.js
You should see the message indicating that the server is running on http://localhost:3000
.
Chapter 2: Adding MongoDB for Data Persistence
In this chapter, you'll set up MongoDB as the database for the application. MongoDB will allow you to persistently store to-do items, making the app more practical.
Step 1: Set Up MongoDB
There are two primary ways to set up MongoDB: locally or using MongoDB Atlas.
-
MongoDB Atlas (Recommended for Cloud):
- Go to MongoDB Atlas and sign up for a free account.
- Create a new project and a Cluster.
- Set up a database user with a username and password.
- Obtain the MongoDB connection string. It should look something like this:
mongodb+srv://<username>:<password>@cluster0.mongodb.net/todo-app?retryWrites=true&w=majority
- Replace
<username>
and<password>
with your MongoDB Atlas credentials.
-
Install MongoDB Locally:
- If you prefer to use MongoDB locally, follow the installation guide.
- After installation, you can start the MongoDB service and use
mongodb://localhost:27017/todo-app
as the connection string.
Step 2: Update the .env
File
In your .env
file, add your MongoDB connection string:
MONGO_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/todo-app?retryWrites=true&w=majority
Replace <username>
and <password>
with your actual MongoDB credentials.
Step 3: Modify index.js
to Connect to MongoDB
With the MONGO_URI
in place, dbConfig.js
will automatically use it when connecting to the database. If you followed the previous chapter's steps, your connectDB
function in dbConfig.js
is already set up to use process.env.MONGO_URI
.
Step 4: Test MongoDB Integration
Start the application with:
node index.js
In a separate terminal, test the /api/todos
endpoint by sending a POST
request to add a new to-do item. You can use curl
or a tool like Postman.
curl --location --request POST 'http://localhost:3000/api/todos' \
--header 'Content-Type: application/json' \
--data-raw '{
"title": "Sample To-Do Item"
}'
You should receive a JSON response with the newly created to-do item. If you configured MongoDB Atlas, you can visit your cluster to view the stored document.
Chapter Summary
In this chapter, you've integrated MongoDB with the application. You can now store to-do items persistently, which will be especially useful when containerizing the app with Docker in the next chapter.
Chapter 3: Dockerizing the Application
Docker makes it easy to create, deploy, and run applications by using containers. In this chapter, you'll create a Dockerfile to containerize the Node.js app and define a Docker Compose file to manage both the app and MongoDB as services.
Step 1: Create a Dockerfile
In the root directory of your project, create a file named Dockerfile
:
# Use the official Node.js image as a base
FROM node:14
# Set the working directory inside the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application files
COPY . .
# Expose the application port
EXPOSE 3000
# Start the application
CMD ["node", "index.js"]
Step 2: Create a Docker Compose File
To manage both the Node.js app and MongoDB, create a docker-compose.yml
file in the root directory:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- PORT=3000
- MONGO_URI=mongodb://mongo:27017/todo-app
depends_on:
- mongo
mongo:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
This file defines:
- app service for the Node.js application, which depends on mongo.
- mongo service using the official MongoDB image, with data stored in a persistent volume.
Step 3: Run the Application with Docker Compose
Build and run the containers:
docker-compose up --build
Access the application at http://localhost:3000
. You can also run the application in the background (detached mode) by adding the -d
flag:
docker-compose up -d
To stop the containers, use:
docker-compose down
Chapter Summary
In this chapter, you created a Dockerfile and a docker-compose.yml file to containerize the application and run both the Node.js app and MongoDB in separate containers. In the next chapter, you'll use Kubernetes to orchestrate these containers for scalability and reliability.
Chapter 4: Deploying the Application on Kubernetes
Now that you've containerized your application, it's time to deploy it on Kubernetes. Kubernetes is a powerful container orchestration platform that makes it easy to manage, scale, and deploy containerized applications. In this chapter, you'll learn how to set up Kubernetes resources, including Deployments, Services, and PersistentVolumes, to run your Node.js and MongoDB containers.
Step 1: Set Up Kubernetes Configuration Files
Create a k8s
folder in your project directory to store the Kubernetes configuration files.
mkdir k8s
Step 2: Define MongoDB Deployment and Service
- In the
k8s
folder, create a file namedmongo-deployment.yml
to define a PersistentVolumeClaim, Deployment, and Service for MongoDB.
# k8s/mongo-deployment.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongo-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
spec:
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:latest
ports:
- containerPort: 27017
volumeMounts:
- name: mongo-storage
mountPath: /data/db
volumes:
- name: mongo-storage
persistentVolumeClaim:
claimName: mongo-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mongo
spec:
selector:
app: mongo
ports:
- protocol: TCP
port: 27017
targetPort: 27017
In this configuration:
- PersistentVolumeClaim allocates 1 GB of storage for MongoDB.
- Deployment specifies the
mongo
container with a volume mount for data persistence. - Service exposes MongoDB within the cluster on port
27017
.
Step 3: Define Node.js App Deployment and Service
- In the
k8s
folder, create a file namedapp-deployment.yml
for the Node.js application.
# k8s/app-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-app
spec:
selector:
matchLabels:
app: todo-app
template:
metadata:
labels:
app: todo-app
spec:
containers:
- name: todo-app
image: your-dockerhub-username/todo-app:latest # Replace with your Docker Hub image
ports:
- containerPort: 3000
env:
- name: MONGO_URI
value: "mongodb://mongo:27017/todo-app"
- name: PORT
value: "3000"
---
apiVersion: v1
kind: Service
metadata:
name: todo-app-service
spec:
selector:
app: todo-app
ports:
- protocol: TCP
port: 3000
targetPort: 3000
type: LoadBalancer
In this configuration:
- Deployment defines the
todo-app
container and sets environment variables likeMONGO_URI
. - Service exposes the app using a LoadBalancer service, which is useful for external access on platforms like Minikube or Docker Desktop.
Step 4: Push the Docker Image to a Registry
If you haven't pushed the Node.js Docker image to a registry like Docker Hub, follow these steps:
-
Log in to Docker Hub:
docker login
-
Tag the image:
docker tag todo-app:latest your-dockerhub-username/todo-app:latest
-
Push the image:
docker push your-dockerhub-username/todo-app:latest
Step 5: Deploy to Kubernetes
Use kubectl
to deploy the MongoDB and Node.js app to your Kubernetes cluster:
-
Deploy MongoDB:
kubectl apply -f k8s/mongo-deployment.yml
-
Deploy the Node.js app:
kubectl apply -f k8s/app-deployment.yml
-
Check the status of your deployments and services:
kubectl get deployments kubectl get pods kubectl get services
-
You should see your mongo and todo-app services listed, and the todo-app-service should be of type
LoadBalancer
with an EXTERNAL-IP (depending on your environment).
Step 6: Access the Application
If you're using port forwarding or running Kubernetes locally with Minikube or Docker Desktop, you can access the application:
-
Use port forwarding to expose the Node.js app:
kubectl port-forward service/todo-app-service 3000:3000
-
Visit the application at
http://localhost:3000
.
For Minikube users, you can use the Minikube tunnel to access the LoadBalancer IP:
minikube tunnel
Chapter Summary
In this chapter, you've deployed your Node.js application and MongoDB database to Kubernetes, allowing for easy scaling and robust orchestration. In the next chapter, you'll learn how to access and debug the application.
Chapter 5: Accessing and Debugging the Application
Now that your application is running on Kubernetes, it's essential to know how to access and troubleshoot it. In this chapter, you'll learn techniques to monitor the health of your application, access logs, and debug any issues that might arise.
Step 1: Verifying Application Health
After deploying, you can use kubectl
commands to check the status of your Pods, Deployments, and Services.
-
Get all Pods:
kubectl get pods
- This command lists all the Pods in the cluster, showing their statuses (e.g.,
Running
,Pending
, orError
).
- This command lists all the Pods in the cluster, showing their statuses (e.g.,
-
Get Deployments:
kubectl get deployments
- Check the
AVAILABLE
andREADY
columns to ensure that your application is correctly scaled and running.
- Check the
-
Get Services:
kubectl get services
- This lists the services, allowing you to confirm that your todo-app-service is accessible through a LoadBalancer or ClusterIP.
Step 2: Accessing Application Logs
If your application is not working as expected, checking the logs is a good starting point. Use kubectl logs
to view the logs of a specific Pod.
-
View Logs for a Specific Pod:
kubectl logs <pod-name>
- Replace
<pod-name>
with the name of the Pod you want to view logs for. You can retrieve the Pod name by runningkubectl get pods
.
- Replace
-
View Real-Time Logs:
kubectl logs -f <pod-name>
- The
-f
option allows you to follow the logs in real time, which is useful for monitoring your application as it handles requests.
- The
Step 3: Debugging Using Port Forwarding
If you need to debug a service directly, you can use port forwarding to expose the service to your local machine.
-
Forward Port 3000 for the Node.js App:
kubectl port-forward service/todo-app-service 3000:3000
- With this command, you can access the application at
http://localhost:3000
and test the API endpoints directly.
- With this command, you can access the application at
-
Access MongoDB for Debugging: If you need to connect to MongoDB, you can forward its port as well:
kubectl port-forward service/mongo 27017:27017
- You can now connect to MongoDB at
mongodb://localhost:27017
using a MongoDB client or a tool like MongoDB Compass.
- You can now connect to MongoDB at
Step 4: Viewing Kubernetes Dashboard (Optional)
The Kubernetes Dashboard provides a web-based interface for monitoring your cluster. To access the dashboard, follow these steps:
-
Start the Kubernetes Dashboard:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.2.0/aio/deploy/recommended.yaml
-
Create a Service Account for the Dashboard:
kubectl create serviceaccount dashboard-admin-sa kubectl create clusterrolebinding dashboard-admin-sa --clusterrole=cluster-admin --serviceaccount=default:dashboard-admin-sa
-
Retrieve the Authentication Token:
kubectl get secret $(kubectl get serviceaccount dashboard-admin-sa -o jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64 --decode
-
Access the Dashboard:
kubectl proxy
- Open a browser and navigate to
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
.
- Open a browser and navigate to
Step 5: Troubleshooting Common Issues
-
Check for Pod Failures:
- If a Pod fails to start, use the
kubectl describe pod <pod-name>
command to view detailed information about its status, environment variables, and events that might indicate why it failed.
- If a Pod fails to start, use the
-
Reapply Configurations:
- Sometimes, configuration changes might not be reflected in the Pods. Use
kubectl apply -f <configuration-file.yml>
to reapply configuration changes orkubectl delete pod <pod-name>
to delete a Pod and let the Deployment recreate it with updated settings.
- Sometimes, configuration changes might not be reflected in the Pods. Use
-
Using kubectl exec:
- If you need to explore inside a Pod, use the following command to open an interactive shell:
kubectl exec -it <pod-name> -- /bin/bash
- This can be particularly useful for debugging, as you can inspect file structures, check environment variables, and even manually run commands to test connectivity.
- If you need to explore inside a Pod, use the following command to open an interactive shell:
Chapter Summary
In this chapter, you learned how to access and troubleshoot your application running on Kubernetes. By using kubectl
commands to check the status of deployments, access logs, and forward ports, you're equipped with essential skills to monitor and debug applications in a Kubernetes environment.
In the next chapter, we'll cover some additional features and potential expansion ideas for this project, such as adding more Kubernetes services, enhancing your Docker setup, and scaling the application.
Chapter 6: Expanding the Application and Scaling with Kubernetes
Now that you have the core To-Do List application up and running on Kubernetes, let's explore how to extend and enhance it. This chapter will cover a few ways to expand the app's functionality, optimize its performance, and scale it using Kubernetes.
Step 1: Scaling the Application
Kubernetes makes it easy to scale your application by increasing the number of replicas for a Deployment. This approach can help handle increased traffic by distributing requests across multiple instances of your application.
-
Update the Deployment File:
- Open
app-deployment.yml
and modify thereplicas
field in thespec
section:
# k8s/app-deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: todo-app spec: replicas: 3 # Scale the application to 3 replicas selector: matchLabels: app: todo-app template: metadata: labels: app: todo-app spec: containers: - name: todo-app image: your-dockerhub-username/todo-app:latest ports: - containerPort: 3000 env: - name: MONGO_URI value: "mongodb://mongo:27017/todo-app" - name: PORT value: "3000"
- Open
-
Apply the Changes:
kubectl apply -f k8s/app-deployment.yml
-
Verify the Scaling:
- Use
kubectl get pods
to see the updated Pods. You should see three instances of thetodo-app
Pod running.
- Use
Step 2: Implement Horizontal Pod Autoscaling
You can configure Kubernetes to automatically scale the number of replicas based on resource usage, such as CPU or memory, using the Horizontal Pod Autoscaler.
-
Create the Autoscaler:
kubectl autoscale deployment todo-app --cpu-percent=50 --min=2 --max=5
- This command creates an autoscaler that adjusts the number of
todo-app
replicas based on CPU usage. The autoscaler will increase the number of Pods when CPU usage exceeds 50%, with a minimum of 2 replicas and a maximum of 5.
- This command creates an autoscaler that adjusts the number of
-
Monitor the Autoscaler:
kubectl get hpa
- This command will display the status of the Horizontal Pod Autoscaler, showing current and target CPU utilization.
Step 3: Adding Liveness and Readiness Probes
Liveness and Readiness probes are used to monitor the health of your application within Kubernetes. These probes help Kubernetes detect and automatically restart Pods that are unhealthy or not yet ready to serve traffic.
-
Update the Node.js Deployment File:
- Open
app-deployment.yml
and add liveness and readiness probes in the container specification:
# k8s/app-deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: todo-app spec: replicas: 3 selector: matchLabels: app: todo-app template: metadata: labels: app: todo-app spec: containers: - name: todo-app image: your-dockerhub-username/todo-app:latest ports: - containerPort: 3000 env: - name: MONGO_URI value: "mongodb://mongo:27017/todo-app" - name: PORT value: "3000" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 5
- Open
-
Add a Health Check Endpoint in
index.js
:- Add a simple health check route to the Express app to support these probes:
app.get('/health', (req, res) => { res.status(200).send('OK'); });
-
Apply the Changes:
kubectl apply -f k8s/app-deployment.yml
Step 4: Setting Up Resource Limits and Requests
To ensure that your application doesn't consume too many resources on the cluster, you can set resource requests and limits for each container.
-
Update
app-deployment.yml
with Resource Specifications:# k8s/app-deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: todo-app spec: replicas: 3 selector: matchLabels: app: todo-app template: metadata: labels: app: todo-app spec: containers: - name: todo-app image: your-dockerhub-username/todo-app:latest ports: - containerPort: 3000 env: - name: MONGO_URI value: "mongodb://mongo:27017/todo-app" - name: PORT value: "3000" resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"
-
Apply the Changes:
kubectl apply -f k8s/app-deployment.yml
With these settings, Kubernetes will ensure that each Pod is guaranteed 64Mi of memory and 250m of CPU, and it will limit each Pod to a maximum of 128Mi of memory and 500m of CPU.
Step 5: Use ConfigMaps and Secrets
ConfigMaps and Secrets allow you to manage configuration data and sensitive information (like API keys) outside of your application code.
-
Create a ConfigMap for Environment Variables:
kubectl create configmap todo-app-config --from-literal=MONGO_URI=mongodb://mongo:27017/todo-app --from-literal=PORT=3000
-
Update the Deployment to Use ConfigMap:
- In
app-deployment.yml
, replace environment variables with the ConfigMap references:
env: - name: MONGO_URI valueFrom: configMapKeyRef: name: todo-app-config key: MONGO_URI - name: PORT valueFrom: configMapKeyRef: name: todo-app-config key: PORT
- In
-
Use Secrets for Sensitive Data:
- To store sensitive information, use
kubectl create secret
:
kubectl create secret generic mongo-secret --from-literal=MONGO_PASSWORD=your_password
- You can then use the secret as an environment variable in your Pod:
env: - name: MONGO_PASSWORD valueFrom: secretKeyRef: name: mongo-secret key: MONGO_PASSWORD
- To store sensitive information, use
Chapter Summary
In this chapter, you explored several ways to enhance the To-Do List application's functionality and performance on Kubernetes. You added autoscaling, liveness and readiness probes, resource limits, and environment configuration with ConfigMaps and Secrets. These features will help make your application more scalable, secure, and resilient.
With this, you've not only built a robust, production-ready application but also learned how to manage, scale, and secure it on Kubernetes!
Conclusion
Congratulations! You've successfully built, containerized, and deployed a To-Do List application using Node.js, Express, MongoDB, Docker, and Kubernetes. Throughout this tutorial, you explored various stages of modern web development, from writing basic API endpoints to implementing sophisticated container orchestration techniques. Here's a recap of what you've learned:
-
Node.js & Express Basics: You started by setting up a Node.js application with Express, creating routes, and handling asynchronous operations to manage to-do items. This covered the fundamentals of RESTful API design, error handling, and middleware usage.
-
MongoDB Integration: You added MongoDB as the database for your application, leveraging Mongoose for object modeling. This provided a persistent data store for your app and helped you understand the flow between a server and database in a typical web app.
-
Docker & Docker Compose: You containerized your application, enabling you to run it reliably across different environments. By defining a Dockerfile and a docker-compose.yml file, you were able to build, manage, and run the app along with its MongoDB dependency in a containerized setup, simplifying deployment and scaling.
-
Kubernetes Deployment: You deployed the application on Kubernetes, configuring Deployments, Services, PersistentVolumeClaims, and ConfigMaps. These resources helped you manage container orchestration, storage, and external access, allowing your app to handle production-like workloads with ease.
-
Advanced Kubernetes Features: To optimize your application further, you explored Horizontal Pod Autoscaling, liveness and readiness probes, resource limits, and ConfigMaps and Secrets. These features improved your app's resilience, scalability, and security, preparing it for real-world deployment.
Sample Application
If you'd like to see the complete, finished application, you can view the source code and project structure on GitHub:
View the Finished To-Do App on GitHub
This repository showcases the entire application, including all the components we've discussed in this tutorial. It's an excellent resource for reviewing the code structure, understanding how different parts of the application interact, and seeing how concepts like middleware, error handling, and API interactions are implemented in a real-world scenario.
By exploring the repository, you can:
- Examine the full project structure
- Review the implementation of Express routes and controllers
- See how MongoDB integration is handled with Mongoose
- Understand the Docker and Kubernetes configurations for deployment
- Explore additional features or improvements that might not have been covered in depth in this tutorial
Feel free to clone the repository, experiment with the code, and use it as a foundation for your own projects or further learning.
Final Thoughts
You now have a strong foundation in building and deploying containerized applications. The skills you've developed through this project are transferable across various tech stacks and are essential in modern software development. As next steps, consider exploring:
- CI/CD Pipelines: Implement continuous integration and continuous deployment pipelines to automate the building, testing, and deployment of your application.
- Advanced Kubernetes Concepts: Look into Kubernetes operators, custom resource definitions (CRDs), and monitoring tools like Prometheus and Grafana to enhance your cluster management capabilities.
- Microservices Architecture: Expand this single application into a suite of microservices, allowing you to manage and deploy individual components independently.
With the knowledge you've gained here, you're well on your way to building robust, scalable, and maintainable applications for a wide range of use cases. Happy coding, and good luck on your journey as a cloud-native developer!