Many have wondered how a simple task sheet or applications that provide such functionality work. In this article, I invite you to consider how you can write your small service in Go in a couple of hours and put everything in a database. Why Goalng? The Keys are Simplicity and Efficiency. Minimalistic Design: Easy to learn and use, focusing on simplicity. Fast Compilation: Compiled to native code, ensuring speedy execution and deployment. Concurrency Goroutines: Lightweight threads managed by Go runtime make concurrent programming more superficial and efficient. Channels: Facilitate safe communication between goroutines, avoiding the pitfalls of shared memory. Robust Standard Library. Rich Ecosystem: Extensive standard library covering web servers, cryptography, I/O, and more. Cross-Platform: Compiles to various operating systems and architectures without modification. Let's Check the Trends: The Rise of Cloud-Native Development and Microservices Docker allows developers to package applications into containers, ensuring consistency across different environments. For example, a web server can be run in a Docker container to ensure it behaves similarly in development and production. Consul Consul provides service discovery and configuration management. K8s (Kubernetes) - automates the deployment, scaling, and management of containerized applications. gRPC is a high-performance, open-source universal RPC framework. Example: Using gRPC to enable efficient communication between microservices written in different languages. Terraform is used to safely and efficiently build, change, and version infrastructure. An example is using Terraform scripts to provision cloud infrastructure on AWS. Golang and the Internet of Things (IoT) NATS is a simple, high-performance, open-source messaging system for cloud-native applications, IoT messaging, and microservices architectures. InfluxData is a platform for handling time series data, which is essential for IoT applications. Growing Adoption of Machine Learning and Artificial Intelligence (AI) “TensorFlow Go” provides machine learning tools that can be used with Go. Gorgonia is a library that brings machine-learning capabilities to Go. GoLearn is a machine-learning library for Go. The Expanding Golang Ecosystem Gin is a high-performance HTTP web framework written in Golang. Viper is a complete configuration solution for Go applications. For example, It Manages application configuration using Viper to support formats like JSON, YAML, and TOML. Cobra is a library that creates powerful modern CLI applications. GORM is an ORM library for Golang. Protocol Buffers (Protobuf) is a method developed by Google for serializing structured data. Why MongoDB? We need to collect data for our tasks and be flexible. We don't need to create a schema or relationship between something. What can we have using it: Flexible Schema: MongoDB allows for schema-less design, making it easy to handle unstructured or semi-structured data. Scalability: It supports horizontal scaling, allowing you to distribute data across multiple servers. Rich Query Language: MongoDB provides a powerful query language, including support for complex queries, indexing, and aggregation. That’s nice for our example:{ "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } A local run with docker: version: '3.1' services: mongo: image: mongo ports: - "27017:27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example Now, we have DB, but we need to work with it as well. Compass MongoDB Compass is a graphical user interface (GUI) for MongoDB designed to facilitate developers, database administrators, and data analysts' interactions with their MongoDB databases. It provides a user-friendly visual representation of the data and powerful tools for querying, managing, and optimizing databases. Download it here: https://www.mongodb.com/products/tools/compass. Why MongoDB Compass is Easy to Use: Graphical Interface: The graphical interface reduces the learning curve associated with using MongoDB, making it accessible to users with varying technical expertise. Drag-and-Drop Functionality: Many features, such as query building and schema design, use drag-and-drop functionality, simplifying complex operations and making them more intuitive. Real-Time Feedback: Compass provides real-time feedback as you interact with your data, allowing you to see the results of your queries and modifications immediately. Comprehensive Documentation and Support: MongoDB Compass is backed by extensive documentation and a supportive community. Users can easily find tutorials, guides, and forums to help them navigate any challenges. Fast Installations Before We Start Install VS code (It's free). Visit https://code.visualstudio.com/. Installing Go Visit golang.org to download the installer for your operating system. Follow the installation instructions provided on the website. Verify the installation by opening a terminal/command prompt and typing: go version And then add the Golang extension: package main import "fmt" func main() { fmt.Println("Hello, World!") } And run it: go run main.go System Design (Small, But Still ;D) The document should have: Title Description Status Our previous JSON file as a reference: JSON { "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } Next step: Create main methods such as CRUD. Create -The Create operation involves adding new records to a database. This is the initial step in data management, where new data entries are inserted into the database. Read - The Read operation retrieves data from the database. It allows users to fetch and display data without modifying it. Update - The Update operation involves modifying existing records in the database. It changes the data within a record while maintaining its identity. Delete—The Delete operation permanently removes records from a database. It is often accompanied by a confirmation step to prevent accidental deletions. We are going to implement only the “CREATE” or add method because I’d like to share a good example. After that, you can implement others. Project structure: todo-list/ │ ├── cmd/ │ └── main.go ├── pkg/ │ └── handler │ └── add_task.go │ └── http_handler.go │ └── mapper │ └── task.go │ └── model │ └── task.go │ └── usecase │ └── task │ └── repository │ └── add_task.go │ └── mongo_repositiry.go │ └── repository.go │ └── service │ └── add_task.go │ └── service.go └── go.mod I want to use the way to separate all responsibilities by folders. Handler - HTTP layer Model - structures for data Use cases - business layers with service and repository. Let's start with a data structure for our app: package model import "go.mongodb.org/mongo-driver/bson/primitive" type Task struct { ID string json:"id" Title string json:"title" Desciption string json:"description" Completed bool json:"completed" } type MongoTask struct { ID primitive.ObjectID json:"id" bson:"_id" Title string json:"title" Desciption string json:"description" Completed bool json:"completed" } Task - for HTTP request, MongoTask - for MongoDb layer. Using two structures is easy because sometimes we don't need to send additional data to our users. For example, we might have a secret field, like a username, which we must hide. Now that we know CRUD, let's code it! Repository layer: type Repository interface { AddTask(ctx context.Context, task model.MongoTask) error } Service layer: type TodoService interface { AddTask(ctx context.Context, task model.Task) error } Let's connect and inject dependencies: // Initialize repository, service, and handler todoRepo := repository.NewMongoRepository(client) todoService := service.NewService(todoRepo) todoHandler := handler.NewHandler(todoService) Finally, context and connections: func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Set MongoDB client options clientOptions := options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{ Username: "root", Password: "example", }) client, err := mongo.Connect(ctx, clientOptions) if err != nil { log.Fatal(err) } err = client.Ping(ctx, nil) if err != nil { log.Fatal(err) } log.Println("Connected to MongoDB!") // Initialize repository, service, and handler todoRepo := repository.NewMongoRepository(client) todoService := service.NewService(todoRepo) todoHandler := handler.NewHandler(todoService) // Set up routes http.HandleFunc("/api/v1/add", todoHandler.AddTask) // Create a server srv := &http.Server{ Addr: ":8080", Handler: nil, } // .. todo } Demo Now, we have everything, and we can start to analyze what happens when we call our service. curl -X POST http://localhost:8080/add #-H "Content-Type: application/json" #-d '{ "id": 1, "title": "Buy groceries", "completed": false #}' POST http://localhost:8080/api/v1/add Content-Type: application/json { "title": "Add description to the structure", "description": "your desc here..." } We will process the request using the handler layer, decode it using JSON lib, and send the model to the service layer. func (h *Handler) AddTask(w http.ResponseWriter, r *http.Request) { ctx := context.Background() var task model.Task err := json.NewDecoder(r.Body).Decode(&task) if err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } err = h.Service.AddTask(ctx, task) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } Next step, process it in the service layer: (just proxy and convert the model to DTO or Entity for MongoDb). func (s *Service) AddTask(ctx context.Context, task model.Task) error { return s.Repo.AddTask(ctx, mapper.MapToDto(task)) } Lastly, use the MongoDB client, and save the task to DB. func (r *MongoRepository) AddTask(ctx context.Context, task model.MongoTask) error { task.ID = primitive.NewObjectID() _, err := r.collection.InsertOne(ctx, task) return err } That's it! We finished the first method for saving the task. You can implement three more methods, or you can check them out here: Golang Workshop. Conclusion In conclusion, we've created a small yet robust task management service using Golang and MongoDB. This exercise demonstrated how Golang's simplicity, concurrency features, and MongoDB's flexibility and scalability provide a powerful platform for building modern web applications. With the right tools and architecture, you can efficiently manage and manipulate data, creating scalable and maintainable services. Now, we know how to build our to-do list and understand that it’s not hard to code. Take care! Many have wondered how a simple task sheet or applications that provide such functionality work. In this article, I invite you to consider how you can write your small service in Go in a couple of hours and put everything in a database. Why Goalng? The Keys are Simplicity and Efficiency. Minimalistic Design: Easy to learn and use, focusing on simplicity. Minimalistic Design : Easy to learn and use, focusing on simplicity. Minimalistic Design Fast Compilation: Compiled to native code, ensuring speedy execution and deployment. Fast Compilation : Compiled to native code, ensuring speedy execution and deployment. Fast Compilation Concurrency Goroutines: Lightweight threads managed by Go runtime make concurrent programming more superficial and efficient. Channels: Facilitate safe communication between goroutines, avoiding the pitfalls of shared memory. Robust Standard Library. Concurrency Goroutines : Lightweight threads managed by Go runtime make concurrent programming more superficial and efficient. Channels: Facilitate safe communication between goroutines, avoiding the pitfalls of shared memory. Robust Standard Library. Concurrency Goroutines Rich Ecosystem: Extensive standard library covering web servers, cryptography, I/O, and more. Rich Ecosystem : Extensive standard library covering web servers, cryptography, I/O, and more. Rich Ecosystem Cross-Platform: Compiles to various operating systems and architectures without modification. Cross-Platform: Compiles to various operating systems and architectures without modification. Cross-Platform: Let's Check the Trends: Let's Check the Trends: The Rise of Cloud-Native Development and Microservices The Rise of Cloud-Native Development and Microservices Docker allows developers to package applications into containers, ensuring consistency across different environments. For example, a web server can be run in a Docker container to ensure it behaves similarly in development and production. Consul Consul provides service discovery and configuration management. Docker allows developers to package applications into containers, ensuring consistency across different environments. For example, a web server can be run in a Docker container to ensure it behaves similarly in development and production. Consul Consul provides service discovery and configuration management. Docker K8s (Kubernetes) - automates the deployment, scaling, and management of containerized applications. K8s (Kubernetes) - automates the deployment, scaling, and management of containerized applications. K8s gRPC is a high-performance, open-source universal RPC framework. Example: Using gRPC to enable efficient communication between microservices written in different languages. gRPC is a high-performance, open-source universal RPC framework. Example: Using gRPC to enable efficient communication between microservices written in different languages. gRPC Terraform is used to safely and efficiently build, change, and version infrastructure. An example is using Terraform scripts to provision cloud infrastructure on AWS. Terraform is used to safely and efficiently build, change, and version infrastructure. An example is using Terraform scripts to provision cloud infrastructure on AWS. Terraform Golang and the Internet of Things (IoT) NATS is a simple, high-performance, open-source messaging system for cloud-native applications, IoT messaging, and microservices architectures. NATS is a simple, high-performance, open-source messaging system for cloud-native applications, IoT messaging, and microservices architectures. NATS InfluxData is a platform for handling time series data, which is essential for IoT applications. InfluxData is a platform for handling time series data, which is essential for IoT applications. InfluxData Growing Adoption of Machine Learning and Artificial Intelligence (AI) “TensorFlow Go” provides machine learning tools that can be used with Go. “TensorFlow Go” provides machine learning tools that can be used with Go. “TensorFlow Go” Gorgonia is a library that brings machine-learning capabilities to Go. Gorgonia is a library that brings machine-learning capabilities to Go. Gorgonia GoLearn is a machine-learning library for Go. GoLearn is a machine-learning library for Go. GoLearn The Expanding Golang Ecosystem Gin is a high-performance HTTP web framework written in Golang. Gin is a high-performance HTTP web framework written in Golang. Gin Viper is a complete configuration solution for Go applications. For example, It Manages application configuration using Viper to support formats like JSON, YAML, and TOML. Viper is a complete configuration solution for Go applications. For example, It Manages application configuration using Viper to support formats like JSON, YAML, and TOML. Viper Cobra is a library that creates powerful modern CLI applications. Cobra is a library that creates powerful modern CLI applications. Cobra GORM is an ORM library for Golang. GORM is an ORM library for Golang. GORM Protocol Buffers (Protobuf) is a method developed by Google for serializing structured data. Protocol Buffers (Protobuf) is a method developed by Google for serializing structured data. Protocol Buffers (Protobuf) Why MongoDB? We need to collect data for our tasks and be flexible. We don't need to create a schema or relationship between something. What can we have using it: Flexible Schema: MongoDB allows for schema-less design, making it easy to handle unstructured or semi-structured data. Flexible Schema: MongoDB allows for schema-less design, making it easy to handle unstructured or semi-structured data. Scalability: It supports horizontal scaling, allowing you to distribute data across multiple servers. Scalability: It supports horizontal scaling, allowing you to distribute data across multiple servers. Rich Query Language: MongoDB provides a powerful query language, including support for complex queries, indexing, and aggregation. That’s nice for our example:{ "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } Rich Query Language: MongoDB provides a powerful query language, including support for complex queries, indexing, and aggregation. That’s nice for our example: { "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } Rich Query Language: MongoDB provides a powerful query language, including support for complex queries, indexing, and aggregation. That’s nice for our example: { "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } { "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } A local run with docker: version: '3.1' services: mongo: image: mongo ports: - "27017:27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example version: '3.1' services: mongo: image: mongo ports: - "27017:27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example Now, we have DB, but we need to work with it as well. Compass MongoDB Compass is a graphical user interface (GUI) for MongoDB designed to facilitate developers, database administrators, and data analysts' interactions with their MongoDB databases. It provides a user-friendly visual representation of the data and powerful tools for querying, managing, and optimizing databases. Download it here: https://www.mongodb.com/products/tools/compass. Download it here: https://www.mongodb.com/products/tools/compass . https://www.mongodb.com/products/tools/compass Why MongoDB Compass is Easy to Use: Graphical Interface: The graphical interface reduces the learning curve associated with using MongoDB, making it accessible to users with varying technical expertise. Graphical Interface: The graphical interface reduces the learning curve associated with using MongoDB, making it accessible to users with varying technical expertise. Drag-and-Drop Functionality: Many features, such as query building and schema design, use drag-and-drop functionality, simplifying complex operations and making them more intuitive. Drag-and-Drop Functionality: Many features, such as query building and schema design, use drag-and-drop functionality, simplifying complex operations and making them more intuitive. Real-Time Feedback: Compass provides real-time feedback as you interact with your data, allowing you to see the results of your queries and modifications immediately. Real-Time Feedback: Compass provides real-time feedback as you interact with your data, allowing you to see the results of your queries and modifications immediately. Comprehensive Documentation and Support: MongoDB Compass is backed by extensive documentation and a supportive community. Users can easily find tutorials, guides, and forums to help them navigate any challenges. Comprehensive Documentation and Support: MongoDB Compass is backed by extensive documentation and a supportive community. Users can easily find tutorials, guides, and forums to help them navigate any challenges. Fast Installations Before We Start Install VS code (It's free). Install VS code (It's free). Visit https://code.visualstudio.com/ . https://code.visualstudio.com/ Installing Go Visit golang.org to download the installer for your operating system. Installing Go golang.org Follow the installation instructions provided on the website. Verify the installation by opening a terminal/command prompt and typing: Verify go version go version And then add the Golang extension: package main import "fmt" func main() { fmt.Println("Hello, World!") } package main import "fmt" func main() { fmt.Println("Hello, World!") } And run it: go run main.go go run main.go System Design (Small, But Still ;D) The document should have: Title Description Status Title Description Status Our previous JSON file as a reference: JSON { "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } { "_id": "66532b210d9944a92a88ef4b", "title": "Go to the groceries", "description": "Purchase milk, eggs, and bread", "completed": false } Next step: Create main methods such as CRUD. Next step: Create main methods such as CRUD. Create -The Create operation involves adding new records to a database. This is the initial step in data management, where new data entries are inserted into the database. Create Read - The Read operation retrieves data from the database. It allows users to fetch and display data without modifying it. Read Update - The Update operation involves modifying existing records in the database. It changes the data within a record while maintaining its identity. Update Delete —The Delete operation permanently removes records from a database. It is often accompanied by a confirmation step to prevent accidental deletions. Delete We are going to implement only the “CREATE” or add method because I’d like to share a good example. After that, you can implement others. Project structure: Project structure: todo-list/ │ ├── cmd/ │ └── main.go ├── pkg/ │ └── handler │ └── add_task.go │ └── http_handler.go │ └── mapper │ └── task.go │ └── model │ └── task.go │ └── usecase │ └── task │ └── repository │ └── add_task.go │ └── mongo_repositiry.go │ └── repository.go │ └── service │ └── add_task.go │ └── service.go └── go.mod todo-list/ │ ├── cmd/ │ └── main.go ├── pkg/ │ └── handler │ └── add_task.go │ └── http_handler.go │ └── mapper │ └── task.go │ └── model │ └── task.go │ └── usecase │ └── task │ └── repository │ └── add_task.go │ └── mongo_repositiry.go │ └── repository.go │ └── service │ └── add_task.go │ └── service.go └── go.mod I want to use the way to separate all responsibilities by folders. Handler - HTTP layer Model - structures for data Use cases - business layers with service and repository. Handler - HTTP layer Model - structures for data Use cases - business layers with service and repository. Let's start with a data structure for our app: package model import "go.mongodb.org/mongo-driver/bson/primitive" type Task struct { ID string json:"id" Title string json:"title" Desciption string json:"description" Completed bool json:"completed" } type MongoTask struct { ID primitive.ObjectID json:"id" bson:"_id" Title string json:"title" Desciption string json:"description" Completed bool json:"completed" } package model import "go.mongodb.org/mongo-driver/bson/primitive" type Task struct { ID string json:"id" Title string json:"title" Desciption string json:"description" Completed bool json:"completed" } type MongoTask struct { ID primitive.ObjectID json:"id" bson:"_id" Title string json:"title" Desciption string json:"description" Completed bool json:"completed" } Task - for HTTP request, MongoTask - for MongoDb layer. Using two structures is easy because sometimes we don't need to send additional data to our users. For example, we might have a secret field, like a username, which we must hide. Now that we know CRUD, let's code it! Repository layer: type Repository interface { AddTask(ctx context.Context, task model.MongoTask) error } type Repository interface { AddTask(ctx context.Context, task model.MongoTask) error } Service layer: type TodoService interface { AddTask(ctx context.Context, task model.Task) error } type TodoService interface { AddTask(ctx context.Context, task model.Task) error } Let's connect and inject dependencies: // Initialize repository, service, and handler todoRepo := repository.NewMongoRepository(client) todoService := service.NewService(todoRepo) todoHandler := handler.NewHandler(todoService) // Initialize repository, service, and handler todoRepo := repository.NewMongoRepository(client) todoService := service.NewService(todoRepo) todoHandler := handler.NewHandler(todoService) Finally, context and connections: func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Set MongoDB client options clientOptions := options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{ Username: "root", Password: "example", }) client, err := mongo.Connect(ctx, clientOptions) if err != nil { log.Fatal(err) } err = client.Ping(ctx, nil) if err != nil { log.Fatal(err) } log.Println("Connected to MongoDB!") // Initialize repository, service, and handler todoRepo := repository.NewMongoRepository(client) todoService := service.NewService(todoRepo) todoHandler := handler.NewHandler(todoService) // Set up routes http.HandleFunc("/api/v1/add", todoHandler.AddTask) // Create a server srv := &http.Server{ Addr: ":8080", Handler: nil, } // .. todo } func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Set MongoDB client options clientOptions := options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{ Username: "root", Password: "example", }) client, err := mongo.Connect(ctx, clientOptions) if err != nil { log.Fatal(err) } err = client.Ping(ctx, nil) if err != nil { log.Fatal(err) } log.Println("Connected to MongoDB!") // Initialize repository, service, and handler todoRepo := repository.NewMongoRepository(client) todoService := service.NewService(todoRepo) todoHandler := handler.NewHandler(todoService) // Set up routes http.HandleFunc("/api/v1/add", todoHandler.AddTask) // Create a server srv := &http.Server{ Addr: ":8080", Handler: nil, } // .. todo } Demo Now, we have everything, and we can start to analyze what happens when we call our service. curl -X POST http://localhost:8080/add #-H "Content-Type: application/json" #-d '{ "id": 1, "title": "Buy groceries", "completed": false #}' POST http://localhost:8080/api/v1/add Content-Type: application/json { "title": "Add description to the structure", "description": "your desc here..." } curl -X POST http://localhost:8080/add #-H "Content-Type: application/json" #-d '{ "id": 1, "title": "Buy groceries", "completed": false #}' POST http://localhost:8080/api/v1/add Content-Type: application/json { "title": "Add description to the structure", "description": "your desc here..." } We will process the request using the handler layer, decode it using JSON lib, and send the model to the service layer. func (h *Handler) AddTask(w http.ResponseWriter, r *http.Request) { ctx := context.Background() var task model.Task err := json.NewDecoder(r.Body).Decode(&task) if err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } err = h.Service.AddTask(ctx, task) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } func (h *Handler) AddTask(w http.ResponseWriter, r *http.Request) { ctx := context.Background() var task model.Task err := json.NewDecoder(r.Body).Decode(&task) if err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } err = h.Service.AddTask(ctx, task) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } Next step, process it in the service layer: (just proxy and convert the model to DTO or Entity for MongoDb). func (s *Service) AddTask(ctx context.Context, task model.Task) error { return s.Repo.AddTask(ctx, mapper.MapToDto(task)) } func (s *Service) AddTask(ctx context.Context, task model.Task) error { return s.Repo.AddTask(ctx, mapper.MapToDto(task)) } Lastly, use the MongoDB client, and save the task to DB. func (r *MongoRepository) AddTask(ctx context.Context, task model.MongoTask) error { task.ID = primitive.NewObjectID() _, err := r.collection.InsertOne(ctx, task) return err } func (r *MongoRepository) AddTask(ctx context.Context, task model.MongoTask) error { task.ID = primitive.NewObjectID() _, err := r.collection.InsertOne(ctx, task) return err } That's it! We finished the first method for saving the task. You can implement three more methods, or you can check them out here: Golang Workshop. Golang Workshop . Golang Workshop Conclusion In conclusion, we've created a small yet robust task management service using Golang and MongoDB. This exercise demonstrated how Golang's simplicity, concurrency features, and MongoDB's flexibility and scalability provide a powerful platform for building modern web applications. With the right tools and architecture, you can efficiently manage and manipulate data, creating scalable and maintainable services. Now, we know how to build our to-do list and understand that it’s not hard to code. Take care!