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.
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.
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.
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:
TypeScript1export 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.
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
:
TypeScript1import { 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.
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
:
TypeScript1import { 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 thefindAll
method to the HTTPGET
request. When aGET
request is made to the/todos
route, thefindAll
method is called. It fetches the ToDo items from the service and passes them along. -
@Render('index')
: This decorator tells NestJS to render theindex
view template when returning the response. ThefindAll
method doesn't return raw data; instead, it returns an object ({ title: 'ToDo List', todos }
) to be used by theindex
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.
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
:
TypeScript1import { 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 theTodoController
as the controller responsible for handling requests related to the/todos
route. - The
providers
array includes theTodoService
, making it available for dependency injection within the module.
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
:
TypeScript1import { 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.
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, XML1<!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, XML1<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.
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!