Lesson 1
Building a Basic MVC App
Building a Basic MVC App

Welcome to the start of our journey in building an MVC-based ToDo application using NestJS! In this lesson, you'll create a basic MVC application that displays a hardcoded list of ToDo items. This foundational step will give you a solid understanding of how Models, Views, and Controllers interact in a NestJS environment.

By the end of this lesson, you'll see how these components come together to build a simple yet functional ToDo application.

What This Lesson Covers

In this lesson, we'll go through:

  • HTTP Basics: Understanding common HTTP methods used in REST APIs.
  • Defining a Model: How to create a Todo model in NestJS.
  • Building a Service: How to manage your data with a TodoService.
  • Creating a Controller: How to link your data with views using a TodoController.
  • Rendering Views: How to display your data using Handlebars templates.

These concepts will set the stage for adding more complex features in future lessons.

HTTP Basics

Before diving into the code, it’s essential to understand some basic HTTP methods, which form the foundation of how web applications interact with servers. These methods define the type of action to be performed for a given resource in RESTful APIs.

GET

  • Purpose: The GET method is used to retrieve data from the server. It does not modify the server's data.
  • Example: When you type a website's URL in your browser and press enter, your browser sends a GET request to the server to fetch and display the web page.

POST

  • Purpose: The POST method is used to send data to the server to create a new resource. This typically involves submitting form data to the server.
  • Example: In future lessons, we'll implement the functionality to create a new ToDo by sending a POST request to the /todos endpoint with the details of the new item.

PUT

  • Purpose: The PUT method is used to update an existing resource on the server with new data.
  • Example: In future lessons, we'll implement a way to update the details of an existing ToDo item using the PUT method. This is commonly done by sending the updated ToDo data to the server.

DELETE

  • Purpose: The DELETE method is used to remove a resource from the server.
  • Example: We'll also implement a DELETE request in future lessons to remove a ToDo item from the list by specifying the item's unique ID. This request tells the server to delete the specified resource.
Defining the ToDo Model

Before anything else, you need to define the structure of your data. In this case, we are working with ToDo items. The Todo model is essentially a class that represents each task in our list.

Here's the code for our ToDo model:

TypeScript
1export class Todo { 2 id: number; 3 title: string; 4 description?: string; 5 6 constructor(id: number, title: string, description?: string) { 7 this.id = id; 8 this.title = title; 9 this.description = description; 10 } 11}

This Todo class acts as the blueprint for each item in the ToDo list. It includes three properties: id, title, and description (which is optional). The constructor method is used to initialize a new ToDo item with the provided values.

This model is the foundation of your data structure, helping you represent a ToDo item consistently throughout your application.

Building the Todo Service

Now that we’ve defined the model, we need a way to manage these ToDo items. In NestJS, the service layer is responsible for handling business logic and managing data. Our TodoService will store an array of ToDo items and provide methods to retrieve them.

Here’s the TodoService:

TypeScript
1import { Injectable } from '@nestjs/common'; 2import { Todo } from './todo.model'; 3 4@Injectable() 5export class TodoService { 6 private todos: Todo[] = [ 7 new Todo(1, 'Learn NestJS', 'Explore the basics of NestJS'), 8 new Todo(2, 'Develop MVC App', 'Build an application using the MVC pattern') 9 ]; 10 11 findAll(): Todo[] { 12 return this.todos; 13 } 14}

The TodoService class contains a private array todos that holds a hardcoded list of ToDo items. The findAll method simply returns this array of ToDo items.

This service manages our data, and it allows for centralizing business logic like data retrieval, which will come in handy when we add more advanced features.

Creating the Todo Controller

With our service ready, the next step is to create a controller. In the MVC pattern, controllers are responsible for handling incoming requests and returning responses to the client. The controller will receive the request, fetch the necessary data from the service, and pass it to the view for rendering.

Here’s the TodoController:

TypeScript
1import { Controller, Get, Render } from '@nestjs/common'; 2import { TodoService } from './todo.service'; 3 4@Controller('todos') 5export class TodoController { 6 constructor(private readonly todoService: TodoService) {} 7 8 @Get() 9 @Render('index') 10 findAll() { 11 const todos = this.todoService.findAll(); 12 return { title: 'ToDo List', todos }; 13 } 14}

Let's break down the decorators used in this controller:

  • @Controller('todos'): This decorator marks the class as a controller. The string 'todos' specifies that this controller will handle all the requests that start with /todos. For example, a request to /todos will be handled by this controller.

  • @Get(): This decorator maps the findAll method to the HTTP GET request. When a GET request is made to the /todos route, the findAll method is called. It fetches the ToDo items from the service and passes them along.

  • @Render('index'): This decorator tells NestJS to render the index view template when returning the response. The findAll method doesn't return raw data; instead, it returns an object ({ title: 'ToDo List', todos }) to be used by the index view.

This controller is responsible for orchestrating the flow of data from the service to the view. The use of these decorators simplifies the routing and rendering process, keeping the controller code concise and focused on handling the requests.

Defining the TodoModule

The TodoModule acts as a container for the controller and service we’ve just built. It helps organize the application and ensures that the necessary dependencies are properly provided.

Here’s how you define the TodoModule:

TypeScript
1import { Module } from '@nestjs/common'; 2import { TodoController } from './todo.controller'; 3import { TodoService } from './todo.service'; 4 5@Module({ 6 controllers: [TodoController], 7 providers: [TodoService], 8}) 9export class TodoModule {}

In this module:

  • The controllers array lists the TodoController as the controller responsible for handling requests related to the /todos route.
  • The providers array includes the TodoService, making it available for dependency injection within the module.
Registering the TodoModule in the AppModule

Once we’ve defined the TodoModule, we need to register it in the AppModule, which acts as the root module of our application. This step ensures that the TodoModule is part of the overall application and that it’s accessible when we start the server.

Here’s how you register the TodoModule in the AppModule:

TypeScript
1import { Module } from '@nestjs/common'; 2import { TodoModule } from './todo/todo.module'; 3import { AppController } from './app.controller'; 4import { AppService } from './app.service'; 5 6@Module({ 7 imports: [TodoModule], 8 controllers: [AppController], 9 providers: [AppService], 10}) 11export class AppModule {}

In the AppModule, the imports array includes the TodoModule, ensuring the ToDo functionality is integrated into the main application.

Rendering the Views

Now, let’s focus on how the data is presented to the user. Views in NestJS are built using Handlebars. The views turn the data into user-friendly HTML.

We start with the layout template, which provides the basic HTML structure for our application:

HTML, XML
1<!DOCTYPE html> 2<html> 3<head> 4 <title>{{title}}</title> 5</head> 6<body> 7 <h1>ToDo App</h1> 8 <nav> 9 <a href="/">Home</a> 10 <a href="/todos">Go to ToDo List</a> 11 </nav> 12 <div class="container"> 13 {{{body}}} 14 </div> 15</body> 16</html>

This layout defines the overall structure of the page. The {{title}} expression dynamically injects the page title, while {{{body}}} renders the specific content for each view.

Now, let’s create the index view, which will display the list of ToDo items:

HTML, XML
1<h1>{{title}}</h1> 2<ul> 3 {{#each todos}} 4 <li>{{this.title}} - {{this.description}}</li> 5 {{/each}} 6</ul>

This view loops through the todos array passed from the controller using {{#each todos}}. For each ToDo item, it displays the title and description. The structure of the layout ensures that the content is properly formatted on the page.

Why This Matters

Understanding the MVC pattern and how it integrates with NestJS is crucial for building scalable and maintainable applications:

  • Structured Code: By separating concerns into models, views, and controllers, you keep your code organized and easy to maintain.
  • Reusability: Each part of the MVC structure can be reused and tested independently, making your application more modular.
  • Scalability: With a clear separation of concerns, your application will be easier to extend with new features in the future.

By setting a strong foundation with this basic example, you are better prepared to add more complex functionalities like creating, updating, and deleting ToDo items in later lessons.

Ready to see it in action? Let's move on to the exercises and run the code to see how it all fits together!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.