One place for hosting & domains

      Create a Simple To-Do App With React

      Introduction

      As the topic implies, we are going to be building a To-Do application with React. Do not expect any surprises such as managing state with a state management library like Flux or Redux. I promise it will strictly be React. Maybe in the following articles, we can employ something like Redux but we want to focus on React and make sure everybody is good with React itself.

      You don’t need much requirements to setup this project because we will make use of CodePen for demos. You can follow the demo or setup a new CodePen pen. You just need to import React and ReactDOM library:

      <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>To-Do</title>
      </head>
      <body>
      
        <div class="container">
          <div id="container" class="col-md-8 col-md-offset-2">  </div>
        </div>
      
        <script src="https://fb.me/react-15.1.0.js"></script>
        <script src="https://fb.me/react-dom-15.1.0.js"></script>
      
      </body>
      </html>
      

      ReactDOM is a standalone library that is used to render React components on the DOM.

      There are two types of components. These types are not just react-based but can be visualized in any other component-based UI library or framework. They include:

      • Presentation Component
      • Container Component

      Presentation Component: These are contained components that are responsible for UI. They are composed with JSX and rendered using the render method. The key rule about this type of component is that they are stateless meaning that no state of any sort is needed in such components. Data is kept in sync using props.

      If all that a presentation component does is render HTML based on props, then you can use stateless function to define the component rather than classes.

      Container Component: This type of component complements the presentation component by providing states. It’s always the guy at the top of the family tree, making sure that data is coordinated.

      You do not necessarily need a state management tool outside of what React provides if what you are building does not have too many nested children and less complex. A To-Do is simple so we can do with what React offers for now provided we understand how and when to use a presentation or container component

      It is a recommended practice to have a rough visual representation of what you are about to build. This practice is becoming very important when it comes to component-based designs because it is easier to recognize presentation components.

      Todo Sketch

      Your image must not be a clean sketch made with a sketch app. It can just be pencil work. The most important thing is that you have a visual representation of the task at hand.

      From the above diagram, we can fish out our presentation components:

      • TodoForm : purple
      • Title: green
      • TodoList: red
      • Todo: grey

      Todo Form

      Functional components (a.k.a stateless components) are good for presentation components because they are simple to manage and reason about when compared with class components.

      For that sake, we will create the first presentation component, TodoForm, with a functional component:

      const TodoForm = ({addTodo}) => {
        
        let input;
      
        return (
          <div>
            <input ref={node => {
              input = node;
            }} />
            <button onClick={() => {
              addTodo(input.value);
              input.value = '';
            }}>
              +
            </button>
          </div>
        );
      };
      

      Functional components just receive props (which we destructured with ES6) as arguments and return JSX to be rendered. TodoForm has just one prop which is a handler that handles the click event for adding a new todo.

      The value of the input is passed to the input member variable using React’s ref.

      Todo and Todo List

      These components present the list of to-do. TodoList is a ul element that contains a loop of Todo components (made of li elements`):

      const Todo = ({todo, remove}) => {
        
        return (<li onClick(remove(todo.id))>{todo.text}</li>);
      }
      
      const TodoList = ({todos, remove}) => {
        
        const todoNode = todos.map((todo) => {
          return (<Todo todo={todo} key={todo.id} remove={remove}/>)
        });
        return (<ul>{todoNode}</ul>);
      }
      

      See the Pen AXNJpJ by Chris Nwamba (@christiannwamba) on CodePen.

      The remove property is an event handler that will be called when the list item is clicked. The idea is to delete an item when it is clicked. This will be taken care of in the container component.

      The only way the remove property can be passed to the Todo component is via its parent (not grand-parent). For this sake, in as much as the container component that will own TodoList should handle item removal, we still have to pass down the handler from grand-parent to grand-child through the parent.

      This is a common challenge that you will encounter in a nested component when building React applications. If the nesting is going to be deep, it is advised you use container components to split the hierarchy.

      Title

      The title component just shows the title of the application:

      const Title = () => {
        return (
          <div>
             <div>
                <h1>to-do</h1>
             </div>
          </div>
        );
      }
      

      This will eventually become the heart of this application by regulating props and managing state among the presentation components. We already have a form and a list that are independent of each other but we need to do some tying together where needed.

      
      
      window.id = 0;
      
      class TodoApp extends React.Component{
        constructor(props){
          
          super(props);
          
          this.state = {
            data: []
          }
        }
        
        addTodo(val){
          
          const todo = {text: val, id: window.id++}
          
          this.state.data.push(todo);
          
          this.setState({data: this.state.data});
        }
        
        handleRemove(id){
          
          const remainder = this.state.data.filter((todo) => {
            if(todo.id !== id) return todo;
          });
          
          this.setState({data: remainder});
        }
      
        render(){
          
          return (
            <div>
            <Title />
              <TodoForm addTodo={this.addTodo.bind(this)}/>
              <TodoList
                todos={this.state.data}
                remove={this.handleRemove.bind(this)}
              />
            </div>
          );
        }
      }
      

      View CodePen Progress

      We first set up the component’s constructor by passing props to the parent class and setting the initial state of our application.

      Next, we create handlers for adding and removing todo which the events are fired in TodoForm component and Todo component respectively. setState method is used to update the application state at any point.

      As usual, we render the JSX passing in our props which will be received by the child components.

      We have been rendering our demo components to the browser without discussing how but can be seen in the CodePen samples. React abstracts rendering to a different library called ReactDOM which takes your app’s root component and renders it on a provided DOM using an exposed render method:

      ReactDOM.render(<TodoApp />, document.getElementById('container'));
      

      The first argument is the component to be rendered and the second argument is the DOM element to render on.

      We could step up our game by working with an HTTP server rather than just a simple local array. We do not have to bear the weight of jQuery to make HTTP requests, rather we can make use of a smaller library like Axios.

      <script src="https://npmcdn.com/axios/dist/axios.min.js"></script>
      

      React lifecycle methods help you hook into React process and perform some actions. An example is doing something once a component is ready. This is done in the componentDidMount lifecycle method. Lifecycle methods are just like normal class methods and cannot be used in a stateless component.

      class TodoApp extends React.Component{
        constructor(props){
          
          super(props);
          
          this.state = {
            data: []
          }
          this.apiUrl = 'https://57b1924b46b57d1100a3c3f8.mockapi.io/api/todos'
        }
      
        
        componentDidMount(){
          
          axios.get(this.apiUrl)
            .then((res) => {
              
              this.setState({data:res.data});
            });
        }
      }
      

      Mock API is a good mock backend for building frontend apps that needs to consume an API in the future. We store the API URL provided by Mock API as a class property so it can be accessed by different members of the class just as the componentDidMount lifecycle method is. Once there is a response and the promise resolves, we update the state using:

      this.setState()
      

      The add and remove methods now works with the API but also optimized for better user experience. We do not have to reload data when there is new todo, we just push to the existing array. Same with remove:

        
        addTodo(val){
          
          const todo = {text: val}
          
          axios.post(this.apiUrl, todo)
             .then((res) => {
                this.state.data.push(res.data);
                this.setState({data: this.state.data});
             });
        }
      
        
        handleRemove(id){
          
          const remainder = this.state.data.filter((todo) => {
            if(todo.id !== id) return todo;
          });
          
          axios.delete(this.apiUrl+'/'+id)
            .then((res) => {
              this.setState({data: remainder});
            })
        }
      

      We could keep track of the total items in our To-Do with the Title component. This one is easy, place a property on the Title component to store the count and pass down the computed count from TodoApp:

      
      const Title = ({todoCount}) => {
        return (
          <div>
             <div>
                <h1>to-do ({todoCount})</h1>
             </div>
          </div>
        );
      }
      
      
      class TodoApp extends React.Component{
       
       render(){
          
          return (
            <div>
              <Title todoCount={this.state.data.length}/>
              {}
            </div>
          );
        }
        
      }
      

      The app works as expected but is not pretty enough for consumption. Bootstrap can take care of that.

      We violated minor best practices for brevity but most importantly, you get the idea of how to build a React app following community-recommended patterns.

      As I mentioned earlier, you don’t need to use a state management library in React applications if your application is simpler. Anytime you have doubt if you need them or not, then you don’t need them. (YAGNI).

      Building a modern app using Nest.js, MongoDB, and Vue.js

      Introduction

      Nest.js introduces a modern way of building Node.js apps by giving them a proper and modular structure out of the box. It was fully built with TypeScript but still preserves compatibility with plain JavaScript.

      In this post, I will introduce and explain the fundamental steps to follow in order to combine this awesome framework with a modern frontend JavaScript framework such as Vue.js. You will build a web application to manage customers’ information. The application will be used to create a new customer, add several details about the customer, and update each customer’s records in the database.

      The approach to this post will be to build a separate REST API backend with Nest.js and a frontend to consume this API using Vue.js. So basically, instead of building Nest.js application that uses a Node.js template engine for the client-side, you will leverage the awesomeness of Vue.js as a progressive JavaScript library to quickly render contents and handled every client-side related logic.

      In addition, you will use MongoDB database to persist and retrieve data for the application. MongoDB is a schema-less NoSQL document database that can receive and store data in JSON document format. It is often used with Mongoose; an Object Data Modeling (ODM) library, that helps to manage relationships between data and provides schema validations. You learn more about this later in this tutorial.

      • A reasonable knowledge of building applications with JavaScript is required and basic knowledge of TypeScript will be an added advantage.
      • Ensure that you have Node and npm installed on your local system. Check this link for Node and here for instructions on how to install npm.
      • Read this article here on scotch.io to grasp the fundamental knowledge of the building blocks of Nest.js.
      • Install MongoDB on your local system. Follow the instructions here to download and installed it for your choice of the operating system. This tutorial uses MacOS machine for development. To successfully install MongoDB, you can either install it by using homebrew on Mac or by downloading it from the MongoDB website.
      • A text editor installed, such as Visual Studio Code, Atom, or Sublime Text

      Nest.js has a reputation for bringing design patterns and mature structures to the Node.js world. This makes it quite easy to use as the right tool for building awesome web applications. For the fact that Nest.js uses Express library under the hood.

      Nest.js is fully featured and structured to support MVC (Model-View-Controller) design pattern.

      This means you can install one of the popular template engines used in Node.js and configure it to handle the flow of the application and interaction with backend API from the front end.

      While this might be sufficient for a small app, it is always better to consider a much better and contemporary approach to handling the frontend-related part of an application by leveraging on a tool like Vue.js.

      Vue can be used to set up the frontend logic of your application as you will see later in this post.

      Vue.js is a progressive JavaScript framework for building reusable components for user interfaces. It is simple and yet very powerful and ideal for any project. This makes it seamless to use for a Nest.js application for example.

      As you proceed in this tutorial, you will learn how to use and successfully combine these two tools, that is, Nest.js and Vue.js to build a highly interactive web app.

      As mentioned earlier in this post, you will build a customer list management application. To keep things really simple here, we will not be implementing authentication and authorization for any user. The main objective is for you to get conversant and comfortable using both Nest.js and Vue.js. At the end of the day, you would have learned the means to craft and structure this application as shown below:

      We will use Nest.js to develop the backend API and then a Vue.js application to build components for creating, editing, deleting, and showing the total list of customers from a MongoDB database.

      Now that the basic introductory contents have been properly covered, you will proceed to install Nest.js and its required dependencies. Getting Nest.js installed can be done in two different ways:

      • Scaffold the base project with Nest CLI tool
      • Installing the starter project on GitHub by using Git

      You will use the Nest CLI here in this tutorial to easily craft a new Nest.js project. This comes with a lot of benefits like scaffolding a new project seamlessly, generating different files by using the nest command amongst other things.

      First, you need to install the CLI globally on your system. To do that, run the following command from the terminal:

      1. npm install -g @nestjs/cli

      The installation of this tool will give you access to the nest command to manage the project and create Nest.js application-related files as you will see later in this post.

      Now that you have the CLI installed, you can now proceed to create the project for this tutorial by running the following command from your local development folder:

      1. nest new customer-list-app-backend

      The preceding command will generate a customer-list-app-backend application. Next, change directory into this new project and install the only server dependency for the backend. As mentioned earlier, you will use MongoDB database to persist data for the application. To integrate it with a Nest.js project, you need to install mongoose and the mongoose package built by the Nest.js team. Use the following command for this purpose:

      1. cd customer-list-app-backend
      1. npm install --save @nestjs/mongoose mongoose

      With the installation process properly covered, you can now start the application with:

      1. npm run start

      This will start the application on the default port of 3000. Navigate to http://localhost:3000 from your favorite browser and you should have a page similar to this:

      It is assumed that by now, you have installed MongoDB on your machine as instructed at the beginning of this post. To start the database, open a new terminal so that the application keeps running and run sudo mongod. The preceding command will start the MongoDB service and simultaneously run the database in the background.

      Next, to quickly set up a connection to the database from your application, you will have to import the installed MongooseModule within the root ApplicationModule. To do this, use your preferred code editor to open the project and locate ./src/app.module.ts. Update the contents with the following:

      ./src/app.module.ts

      import { Module } from '@nestjs/common';
      import { AppController } from './app.controller';
      import { AppService } from './app.service';
      import { MongooseModule } from '@nestjs/mongoose';
      @Module({
        imports: [
          MongooseModule.forRoot('mongodb://localhost/customer-app', { useNewUrlParser: true })
        ],
        controllers: [AppController],
        providers: [AppService],
      })
      export class AppModule {}
      

      Here, the Mongoose module for MongoDB uses the forRoot() method to supply the connection to the database.

      Setting up and configuring a database schema, interfaces, and DTO

      To properly structure the kind of data that will be stored in the database for this application, you will create a database schema, a TypeScript, and a data transfer object (DTO).

      Database schema

      Here, you will create a mongoose database schema that will determine the data that should be stored in the database. To begin, navigate to the ./src/ folder and first, create a new folder named customer and within the newly created folder, create another one and call it schemas. Now create a new file within the schemas and name customer.schema.ts. Open this newly created file and paste the following code into it:

      ./src/customer/schemas/customer.schema.ts

      import * as mongoose from 'mongoose';
      
      export const CustomerSchema = new mongoose.Schema({
          first_name: String,
          last_name: String,
          email: String,
          phone: String,
          address: String,
          description: String,
          created_at: { type: Date, default: Date.now }
      })
      

      This will ensure that data with string values will be stored in the database.

      Interfaces

      Next, you will create a TypeScript interface that will be used for type-checking and to determine the type of values that will be received by the application. To set it up, create a new folder named interfaces within the ./src/customer folder. After that, create a new file within the newly created folder and name it customer.interface.ts. Paste the following code in it:

      ./src/customer/interfaces/customer.interface.ts

      import { Document } from 'mongoose';
      
      export interface Customer extends Document {
          readonly first_name: string;
          readonly last_name: string;
          readonly email: string;
          readonly phone: string;
          readonly address: string;
          readonly description: string;
          readonly created_at: Date;
      }
      

      Data transfer object (DTO)

      A data transfer object will define how data will be sent over the network. To do this, create a folder dto inside ./src/customer folder and create a new file create-customer.dto.ts and paste the code below in it:

      ./src/customer/dto/create-customer.dto.ts

      export class CreateCustomerDTO {
          readonly first_name: string;
          readonly last_name: string;
          readonly email: string;
          readonly phone: string;
          readonly address: string;
          readonly description: string;
          readonly created_at: Date;
      }
      

      You are now done with the basic configurations of connecting and interacting with the database

      Generate modules

      A module in Nest.js is identified by the @Module() decorator and it takes in objects such as controllers and providers. Here you will leverage the nest command to easily generate a new module for the customer app. This will ensure that the application is properly structured and more organized. Stop the application, if it is currently running with CTRL+C and run the following command

      1. nest generate module customer

      This will create a new file named customer.module.ts within the src/customer folder and update the root module (i.e., app.module.ts) of the application with the details of the newly created module.

      ./src/customer/customer.module.ts

      import { Module } from '@nestjs/common';
      @Module({})
      export class CustomerModule {}
      

      You will come back to add more content to this module later in this post.

      Generate service

      Service, also known as provider in Nest.js, basically carries out the task of abstracting logic away from controllers. With it in place, a controller will only carry out the functionality of handling HTTP requests from the frontend and delegate the complex tasks to services. Service or provider in Nest.js is identified by adding @Injectable() decorator on top of them.

      Generate a new service using the nest command by running the following command from the terminal within the project directory:

      1. nest generate service customer

      After successfully running the command above, two new files will be created. They are:

      • customer.service.ts: this is the main service file with @Injectable() decorator
      • customer.service.spec.ts: a file for unit testing. You can ignore this file for now as testing will not be covered in this tutorial.

      The customer.service.ts file holds all the logic as regards database interaction for creating and updating every detail of a new customer. In a nutshell, the service will receive a request from the controller, communicate this to the database and return an appropriate response afterward.

      Open the newly created customer.service.ts file and replace the existing code with the following :

      ./src/customer/customer.service.ts

      import { Injectable } from '@nestjs/common';
      import { Model } from 'mongoose';
      import { InjectModel } from '@nestjs/mongoose';
      import { Customer } from './interfaces/customer.interface';
      import { CreateCustomerDTO } from './dto/create-customer.dto';
      
      @Injectable()
      export class CustomerService {
          constructor(@InjectModel('Customer') private readonly customerModel: Model<Customer>) { }
          
          async getAllCustomer(): Promise<Customer[]> {
              const customers = await this.customerModel.find().exec();
              return customers;
          }
          
          async getCustomer(customerID): Promise<Customer> {
              const customer = await this.customerModel.findById(customerID).exec();
              return customer;
          }
          
          async addCustomer(createCustomerDTO: CreateCustomerDTO): Promise<Customer> {
              const newCustomer = await this.customerModel(createCustomerDTO);
              return newCustomer.save();
          }
          
          async updateCustomer(customerID, createCustomerDTO: CreateCustomerDTO): Promise<Customer> {
              const updatedCustomer = await this.customerModel
                  .findByIdAndUpdate(customerID, createCustomerDTO, { new: true });
              return updatedCustomer;
          }
          
          async deleteCustomer(customerID): Promise<any> {
              const deletedCustomer = await this.customerModel.findByIdAndRemove(customerID);
              return deletedCustomer;
          }
      }
      

      Here, you imported the required module from @nestjs/common, mongoose, and @nestjs/mongoose. In addition, you also imported the interface created earlier named Customer and a data transfer object CreateCustomerDTO.

      In order to be able to seamlessly carry out several database-related activities such as creating a customer, retrieving the list of customers, or just a single customer, you used the @InjectModel method to inject the Customer model into the CustomerService class.

      Next, you created the following methods:

      • getAllCustomer(): to retrieve and return the list of customers from the database
      • getCustomer(): it takes customerID as a parameter and based on that, it will search and return the details of a user identified by that ID.
      • addCustomer(): used to add a new customer to the database
      • updateCustomer(): this method also takes the ID of a customer as an argument and will be used to edit and update the details of such customer in the database.
      • deleteCustomer(): this will be used to delete the details of a particular customer completely from the database.

      Generate controller

      Handling each route within the application is one of the major responsibilities of controllers in Nest.js. Similar to most JavaScript server-side frameworks for the web, several endpoints will be created and any requests sent to such endpoint from the client-side will be mapped to a specific method within the controller and an appropriate response will be returned.

      Again, you will use the nest command to generate the controller for this application. To achieve that, run the following command:

      1. nest generate controller customer

      This command will also generate two new files within the src/customer, they are, customer.controller.spec.ts and customer.controller.ts files respectively. The customer.controller.ts file is the actual controller file and the second one should be ignored for now. Controllers in Nest.js are TypeScript files decorated with @Controller metadata.

      Now open the controller and replace the content with the following code that contains methods to create a new customer, retrieve the details of a particular customer and fetch the list of all customers from the database:

      ./src/customer/customer.controller.ts

      import { Controller, Get, Res, HttpStatus, Post, Body, Put, Query, NotFoundException, Delete, Param } from '@nestjs/common';
      import { CustomerService } from './customer.service';
      import { CreateCustomerDTO } from './dto/create-customer.dto';
      
      @Controller('customer')
      export class CustomerController {
          constructor(private customerService: CustomerService) { }
      
          
          @Post('/create')
          async addCustomer(@Res() res, @Body() createCustomerDTO: CreateCustomerDTO) {
              const customer = await this.customerService.addCustomer(createCustomerDTO);
              return res.status(HttpStatus.OK).json({
                  message: "Customer has been created successfully",
                  customer
              })
          }
      
          
          @Get('customers')
          async getAllCustomer(@Res() res) {
              const customers = await this.customerService.getAllCustomer();
              return res.status(HttpStatus.OK).json(customers);
          }
      
          
          @Get('customer/:customerID')
          async getCustomer(@Res() res, @Param('customerID') customerID) {
              const customer = await this.customerService.getCustomer(customerID);
              if (!customer) throw new NotFoundException('Customer does not exist!');
              return res.status(HttpStatus.OK).json(customer);
          }
      }
      

      In order to interact with the database, the CustomerService was injected into the controller via the class constructor(). The addCustomer() and getAllCustomer() methods will be used to add a new customer’s details and retrieve the list of customers while the getCustomer() receives the customerID as a query parameter and throw an exception if the customer does not exist in the database.

      Next, you need to be able to update and delete the details of a customer where and when necessary. For this, you will add two more methods to the CustomerController class. Open the file again and add this:

      ./src/customer/customer.controller.ts

      ...
      @Controller('customer')
      export class CustomerController {
          constructor(private customerService: CustomerService) { }
          ...
      
          
          @Put('/update')
          async updateCustomer(@Res() res, @Query('customerID') customerID, @Body() createCustomerDTO: CreateCustomerDTO) {
              const customer = await this.customerService.updateCustomer(customerID, createCustomerDTO);
              if (!customer) throw new NotFoundException('Customer does not exist!');
              return res.status(HttpStatus.OK).json({
                  message: 'Customer has been successfully updated',
                  customer
              });
          }
      
          
          @Delete('/delete')
          async deleteCustomer(@Res() res, @Query('customerID') customerID) {
              const customer = await this.customerService.deleteCustomer(customerID);
              if (!customer) throw new NotFoundException('Customer does not exist');
              return res.status(HttpStatus.OK).json({
                  message: 'Customer has been deleted',
                  customer
              })
          }
      }
      

      To keep things properly organized go back to the customer.module.ts and set up the Customer model. Update the content with the following:

      ./src/customer/customer.module.ts

      import { Module } from '@nestjs/common';
      import { CustomerController } from './customer.controller';
      import { CustomerService } from './customer.service';
      import { MongooseModule } from '@nestjs/mongoose';
      import { CustomerSchema } from './schemas/customer.schema';
      @Module({
        imports: [
          MongooseModule.forFeature([{ name: 'Customer', schema: CustomerSchema }])
        ],
        controllers: [CustomerController],
        providers: [CustomerService]
      })
      export class CustomerModule { }
      

      By default, it is forbidden for two separate applications on different ports to interact or share resources with each other unless it is otherwise allowed by one of them, which is often the server-side. In order to allow requests from the client-side that will be built with Vue.js, you will need to enable CORS (Cross-Origin Resource Sharing).

      To do that in Nest.js, you only need to add app.enableCors() to the main.ts file as shown below:

      ./src/main.ts

      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
        app.enableCors(); 
        await app.listen(3000);
      }
      bootstrap();
      

      With this, you have just completed the backend part of the application and can now proceed to build the frontend with Vue.js.

      The team at Vue.js already created an awesome tool named Vue CLI. It is a standard tool that allows you to quickly generate and install a new Vue.js project with ease. You will use that here to create the frontend part of the customer app, but first, you need to install Vue CLI globally on your machine.

      Open a new terminal and run:

      1. npm install -g @vue/cli

      Once the installation process is complete, you can now use the vue create command to craft a new Vue.js project. Run the following command to do that for this project:

      1. vue create customer-list-app-frontend

      Immediately after you hit RETURN, you will be prompted to pick a preset. You can choose manually select features:

      Next, check the features you will need for this project by using the up and down arrow key on your keyboard to navigate through the list of features. Press the spacebar to select a feature from the list. Select Babel, Router, and Linter / Formatter as shown here:

      Hitting RETURN here will show you another list of options.

      For other instructions, type y to use history mode for a router, this will ensure that history mode is enabled within the router file that will automatically be generated for this project.

      Next, select ESLint with error prevention only in order to pick a linter / formatter config. After that, select Lint on save for additional lint features and save your configuration in a dedicated config file for future projects. Type a name for your preset, I named mine vuescotch:

      This will create a Vue.js application in a directory named customer-list-app-frontend and install all its required dependencies.

      You can now change the directory into the newly created project and start the application with:

      1. cd customer-list-app-frontend

      Run the application:

      1. npm run serve

      You can now view the application on http://localhost:8080:

      Axios, a promised-based HTTP client for the browser will be used here to perform HTTP requests from different components within the application. Stop the frontend application from running by hitting CTRL+C from the terminal and run the following command afterward:

      1. npm install axios --save

      Once the installation process is completed, open the customer-list-app-frontend within a code editor and create a new file named helper.js within the src folder. Open the newly created file and paste the following content in it:

      ./src/helper.js

      export const server = {
          baseURL: 'http://localhost:3000'
      }
      

      What you have done here is to define the baseURL for the backend project built with Nest.js. This is just to ensure that you don’t have to start declaring this URL within several Vue.js components that you will create in the next section.

      Vue.js favors building and structuring applications by creating reusable components to give it a proper structure. Vue.js components contain three different sections, which are

      • <template></template>
      • <script></script>
      • <style></style>.

      You will start by creating a component within the application for a user to create a customer. This component will contain a form with few input fields required to accepts details of a customer and once the form is submitted, the details from the input fields will be posted to the server. To achieve this, create a new folder named customer within the ./src/components folder. This newly created folder will house all the components for this application. Next, create another file within the customer folder and name it Create.vue. Open this new file and add the following:

      ./src/components/customer/Create.vue

      <template>
        <div>
          <div class="col-md-12 form-wrapper">
            <h2> Create Customer</h2>
            <form id="create-post-form" @submit.prevent="createCustomer">
              <div class="form-group col-md-12">
                <label for="first_name">First Name</label>
                <input type="text" id="first_name" v-model="first_name" name="title" class="form-control" placeholder="Enter firstname">
              </div>
              <div class="form-group col-md-12">
                <label for="last_name">Last Name</label>
                <input type="text" id="last_name" v-model="last_name" name="title" class="form-control" placeholder="Enter Last name">
              </div>
              <div class="form-group col-md-12">
                <label for="email">Email</label>
                <input type="text" id="email" v-model="email" name="title" class="form-control" placeholder="Enter email">
              </div>
              <div class="form-group col-md-12">
                <label for="phone_number">Phone</label>
                <input type="text" id="phone_number" v-model="phone" name="title" class="form-control" placeholder="Enter Phone number">
              </div>
              <div class="form-group col-md-12">
                <label for="address">Address</label>
                <input type="text" id="address" v-model="address" name="title" class="form-control" placeholder="Enter Address">
              </div>
              <div class="form-group col-md-12">
                  <label for="description">Description</label>
                  <input type="text" id="description" v-model="description" name="description" class="form-control" placeholder="Enter Description">
              </div>
              <div class="form-group col-md-4 pull-right">
                  <button class="btn btn-success" type="submit">Create Customer</button>
              </div>
            </form>
          </div>
        </div>
      </template>
      

      This is the <template></template> section that contains the details of the input fields. Next, paste the following code just after the end of the </template> tag:

      ./src/components/customer/Create.vue

      ...
      <script>
      import axios from "axios";
      import { server } from "../../helper";
      import router from "../../router";
      export default {
        data() {
          return {
            first_name: "",
            last_name: "",
            email: "",
            phone: "",
            address: "",
            description: ""
          };
        },
        methods: {
          createCustomer() {
            let customerData = {
              first_name: this.first_name,
              last_name: this.last_name,
              email: this.email,
              phone: this.phone,
              address: this.address,
              description: this.description
            };
            this.__submitToServer(customerData);
          },
          __submitToServer(data) {
            axios.post(`${server.baseURL}/customer/create`, data).then(data => {
              router.push({ name: "home" });
            });
          }
        }
      };
      </script>
      

      Here, you created a method createCustomer() to receive the details of a customer via the input fields and used Axios to post the data to the server.

      Similar to the CreateCustomer component, you need to create another component to edit the customer’s details. Navigate to ./src/components/customer and create a new file named Edit.vue. Paste the following code in it:

      ./src/components/customer/Edit.vue

      <template>
        <div>
          <h4 class="text-center mt-20">
            <small>
              <button class="btn btn-success" v-on:click="navigate()">View All Customers</button>
            </small>
          </h4>
          <div class="col-md-12 form-wrapper">
            <h2>Edit Customer</h2>
            <form id="create-post-form" @submit.prevent="editCustomer">
              <div class="form-group col-md-12">
                <label for="first_name">First Name</label>
                <input type="text" id="first_name" v-model="customer.first_name" name="title" class="form-control" placeholder="Enter firstname">
              </div>
              <div class="form-group col-md-12">
                <label for="last_name">Last Name</label>
                <input type="text" id="last_name" v-model="customer.last_name" name="title" class="form-control" placeholder="Enter Last name">
              </div>
              <div class="form-group col-md-12">
                <label for="email">Email</label>
                <input type="text" id="email" v-model="customer.email" name="title" class="form-control" placeholder="Enter email">
              </div>
              <div class="form-group col-md-12">
                <label for="phone_number">Phone</label>
                <input type="text" id="phone_number" v-model="customer.phone" name="title" class="form-control" placeholder="Enter Phone number">
              </div>
              <div class="form-group col-md-12">
                <label for="address">Address</label>
                <input type="text" id="address" v-model="customer.address" name="title" class="form-control" placeholder="Enter Address">
              </div>
              <div class="form-group col-md-12">
                <label for="description">Description</label>
                <input type="text" id="description" v-model="customer.description" name="description" class="form-control" placeholder="Enter Description">
              </div>
              <div class="form-group col-md-4 pull-right">
                <button class="btn btn-success" type="submit">Edit Customer</button>
              </div>
            </form>
          </div>
        </div>
      </template>
      <script>
      import { server } from "../../helper";
      import axios from "axios";
      import router from "../../router";
      export default {
        data() {
          return {
            id: 0,
            customer: {}
          };
        },
        created() {
          this.id = this.$route.params.id;
          this.getCustomer();
        },
        methods: {
          editCustomer() {
            let customerData = {
              first_name: this.customer.first_name,
              last_name: this.customer.last_name,
              email: this.customer.email,
              phone: this.customer.phone,
              address: this.customer.address,
              description: this.customer.description
            };
            axios
              .put(
                `${server.baseURL}/customer/update?customerID=${this.id}`,
                customerData
              )
              .then(data => {
                router.push({ name: "home" });
              });
          },
          getCustomer() {
            axios
              .get(`${server.baseURL}/customer/customer/${this.id}`)
              .then(data => (this.customer = data.data));
          },
          navigate() {
            router.go(-1);
          }
        }
      };
      </script>
      

      The route parameter was used here to fetch the details of a customer from the database and populated the inputs fields with it. As a user of the application, you can now edit the details and submit them back to the server.

      The editCustomer() method within the <script></script> was used to send a PUT HTTP request to the server.

      Finally, to fetch and show the complete list of customers from the server, you will create a new component. Navigate to the views folder within the src folder, you should see a Home.vue file, if otherwise, create it and paste this code in it:

      ./src/views/Home.vue

      <template>
        <div class="container-fluid">
          <div class="text-center">
            <h1>Nest Customer List App Tutorial</h1>
            <p>Built with Nest.js, Vue.js, and MongoDB</p>
            <div v-if="customers.length === 0">
              <h2>No customer found at the moment</h2>
            </div>
          </div>
      
          <div class="">
            <table class="table table-bordered">
              <thead class="thead-dark">
                <tr>
                  <th scope="col">Firstname</th>
                  <th scope="col">Lastname</th>
                  <th scope="col">Email</th>
                  <th scope="col">Phone</th>
                  <th scope="col">Address</th>
                  <th scope="col">Description</th>
                  <th scope="col">Actions</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="customer in customers" :key="customer._id">
                  <td>{{ customer.first_name }}</td>
                  <td>{{ customer.last_name }}</td>
                  <td>{{ customer.email }}</td>
                  <td>{{ customer.phone }}</td>
                  <td>{{ customer.address }}</td>
                  <td>{{ customer.description }}</td>
                  <td>
                    <div class="d-flex justify-content-between align-items-center">
                      <div class="btn-group" style="margin-bottom: 20px;">
                        <router-link :to="{name: 'Edit', params: {id: customer._id}}" class="btn btn-sm btn-outline-secondary">Edit Customer</router-link>
                        <button class="btn btn-sm btn-outline-secondary" v-on:click="deleteCustomer(customer._id)">Delete Customer</button>
                      </div>
                    </div>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </template>
      <script>
      import { server } from "../helper";
      import axios from "axios";
      export default {
        data() {
          return {
            customers: []
          };
        },
        created() {
          this.fetchCustomers();
        },
        methods: {
          fetchCustomers() {
            axios
              .get(`${server.baseURL}/customer/customers`)
              .then(data => (this.customers = data.data));
          },
          deleteCustomer(id) {
            axios
              .delete(`${server.baseURL}/customer/delete?customerID=${id}`)
              .then(data => {
                console.log(data);
                window.location.reload();
              });
          }
        }
      };
      </script>
      

      Within <template> section, you created an HTML table to display all customers’ details and used the <router-link> to create a link for editing and to view a single customer by passing the customer._id as a query parameter. And finally, within the <script> section of this file, you created a method named fetchCustomers() to fetch all customers from the database and updated the page with the data returned from the server.

      Open the AppComponent of the application and update it with the links to both Home and Create components by using the content below:

      ./src/App.vue

      <template>
        <div id="app">
          <div id="nav">
            <router-link to="/">Home</router-link> |
            <router-link to="/create">Create</router-link>
          </div>
          <router-view/>
        </div>
      </template>
      
      <style>
      ...
      .form-wrapper {
        width: 500px;
        margin: 0 auto;
      }
      </style>
      

      Also included is a <style></style> section to include styling for the forms.

      Navigate to the index.html file within the public folder and include the CDN file for Bootstrap as shown below. This is just to give the page some default style:

      <!DOCTYPE html>
      <html lang="en">
      <head>
        ...
        
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
        <title>customer-list-app-frontend</title>
      </head>
      <body>
         ...
      </body>
      </html>
      

      Finally, configure the router file within ./src/router.js to include the link to all the required reusable components created so far by updating its content as shown here:

      ./src/router.js

      import Vue from 'vue'
      import Router from 'vue-router'
      import HomeComponent from '@/views/Home';
      import EditComponent from '@/components/customer/Edit';
      import CreateComponent from '@/components/customer/Create';
      Vue.use(Router)
      export default new Router({
        mode: 'history',
        routes: [
          { path: '/', redirect: { name: 'home' } },
          { path: '/home', name: 'home', component: HomeComponent },
          { path: '/create', name: 'Create', component: CreateComponent },
          { path: '/edit/:id', name: 'Edit', component: EditComponent },
        ]
      });
      

      You can now proceed to test the application by running npm run serve to start it and navigate to http://localhost:8080 to view it:

      Ensure that the backend server is running at this moment, if otherwise, navigate to the backend application from a different terminal and run:

      1. npm run start

      Lastly, also ensure that the MongoDB instance is running as well. Use sudo mongod from another terminal on your local system, if it is not running at the moment.

      In this tutorial, you have created a simple customer list management application by using Nest.js and Vue.js. Here, you used Nest.js to build a RESTful backend API and then leveraged on Vue.js to craft a client that consumes the API.

      This has given you an overview of how to structure Nest.js application and integrate it with a MongoDB database.

      I hope you found this tutorial helpful. Don’t hesitate to explore the source code of both application by checking it out here on GitHub.

      Build a To-Do App with Vue.js 2

      Introduction

      Vue is a simple and minimal progressive JavaScript framework that can be used to build powerful web applications incrementally.

      Vue is a lightweight alternative to other JavaScript frameworks like AngularJS. With an intermediate understanding of HTML, CSS, and JS, you should be ready to get up and running with Vue.

      In this article, we will be building a to-do application with Vue while highlighting the bundle of awesomeness that it has to offer.

      Let’s get started!

      We’ll need the Vue CLI to get started. The CLI provides a means of rapidly scaffolding Single Page Applications and in no time you will have an app running with hot-reload, lint-on-save, and production-ready builds.

      Vue CLI offers a zero-configuration development tool for jumpstarting your Vue apps and component.

      A lot of the decisions you have to make regarding how your app scales in the future are taken care of. The Vue CLI comes with an array of templates that provide a self-sufficient, out-of-the-box ready to use package. The currently available templates are:

      • webpack – A full-featured Webpack + Vue-loader setup with hot reload, linting, testing, and CSS extraction.
      • webpack-simple – A simple Webpack + Vue-loader setup for quick prototyping.
      • browserify – A full-featured Browserify + vueify setup with hot-reload, linting & unit testing.
      • browserify-simple – A simple Browserify + vueify setup for quick prototyping.
      • simple – The simplest possible Vue setup in a single HTML file

      Simply put, the Vue CLI is the fastest way to get your apps up and running.

      1. npm install --global vue-cli

      In this tutorial, we will be focusing on the use of single file components instead of instances. We’ll also touch on how to use parent and child components and data exchange between them. Vue’s learning curve is especially gentle when you use single-file components. Additionally, they allow you to place everything regarding a component in one place. When you begin working on large applications, the ability to write reusable components will be a lifesaver.

      Next, we’ll set up our Vue app with the CLI.

      1. vue init webpack todo-app

      You will be prompted to enter a project name, description, author, and Vue build. We will not install Vue-router for our app. You will also be required to enable linting and testing options or the app.
      You can follow my example below.

      Once we have initialized our app, we will need to install the required dependencies.

      1. cd todo-app
      2. npm install

      To serve the app, run:

      1. npm run dev

      This will immediately open your browser and direct you to http://localhost:8080. The page will look as follows.

      To style our application we will use Semantic. Semantic is a development framework that helps create beautiful, responsive layouts using human-friendly HTML. We will also use Sweetalert to prompt users to confirm actions. Sweetalert is a library that provides beautiful alternatives to the default JavaScript alert. Add the minified JavaScript and CSS scripts and links to your index.html file found at the root of your folder structure.

      
      <head>
        <meta charset="utf-8">
        <title>todo-app</title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.7/semantic.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.7/semantic.min.js"></script>
        <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
      </head>
      

      Every Vue app, needs to have a top-level component that serves as the framework for the entire application. For our application, we will have a main component, and nested within shall be a TodoList component. Within this, there will be Todo sub-components.

      Let’s dive into building our application. First, we’ll start with the main top-level component. The Vue CLI already generates a main component that can be found in src/App.vue. We will build out the other necessary components.

      The Vue CLI creates a component Hello during setup that can be found in src/components/Hello.vue. We will create our own component called TodoList.vue and won’t be needing this anymore.

      Inside of the new TodoList.vue file, write the following.

      <template>
        <div>
        <ul>
          <li> Todo A </li>
          <li> Todo B </li>
          <li> Todo C </li>
        </ul>
        </div>
      </template>
      
      <script type="text/javascript">
        export default {
        };
      </script>
      
      <style>
      </style>
      

      A component file consists of three parts; template, component class, and styles sections.

      The template area is the visual part of a component. Behaviour, events, and data storage for the template are handled by the class. The style section serves to further improve the appearance of the template.

      To utilize the component we just created, we need to import it into our main component. Inside of src/App.vue make the following changes just above the script section and below the template closing tag.

      
      import TodoList from './components/TodoList'
      
      import Hello from './components/Hello'
      

      We will also need to reference the TodoList component in the components property and delete the previous reference to Hello component. After the changes, our script should look like this.

      <script>
      import TodoList from './components/TodoList';
      
      export default {
        components: {
          
          TodoList,
        },
      };
      </script>
      

      To render the component, we invoke it like an HTML element. Component words are separated with dashes like below instead of camel case.

      <template>
        <div>
          
          
          <todo-list></todo-list>
        </div>
      </template>
      

      When we have saved our changes our rudimentary app should look like this.

      We will need to supply data to the main component that will be used to display the list of todos. Our todos will have three properties; The title, project, and done (to indicate if the todo is complete or not). Components provide data to their respective templates using a data function. This function returns an object with the properties intended for the template. Let’s add some data to our component.

      export default {
        name: 'app',
        components: {
          TodoList,
        },
        
        data() {
          return {
            todos: [{
              title: 'Todo A',
              project: 'Project A',
              done: false,
            }, {
              title: 'Todo B',
              project: 'Project B',
              done: true,
            }, {
              title: 'Todo C',
              project: 'Project C',
              done: false,
            }, {
              title: 'Todo D',
              project: 'Project D',
              done: false,
            }],
          };
        },
      };
      

      We will need to pass data from the main component to the TodoList component. For this, we will use the v-bind directive. The directive takes an argument which is indicated by a colon after the directive name. Our argument will be todos which tells the v-bind directive to bind the element’s todos attribute to the value of the expression todos.

      <todo-list v-bind:todos="todos"></todo-list>
      

      The todos will now be available in the TodoList component as todos. We will have to modify our TodoList component to access this data. The TodoList component has to declare the properties it will accept when using it. We do this by adding a property to the component class.

      export default {
          props: ['todos'],
      }
      

      Inside our TodoList template let’s loop over the list of todos and also show the number of completed and uncompleted tasks. To render a list of items, we use the v-for directive. The syntax for doing this is represented as v-for="item in items" where items are the array with our data and item is a representation of the array element being iterated on.

      <template>
        <div>
          
          <p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
          <p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
          <div class='ui centered card' v-for="todo in todos">
            <div class='content'>
              <div class='header'>
                {{ todo.title }}
              </div>
              <div class='meta'>
                {{ todo.project }}
              </div>
              <div class='extra content'>
                <span class='right floated edit icon'>
                  <i class='edit icon'></i>
                </span>
              </div>
            </div>
            <div class='ui bottom attached green basic button' v-show="todo.done">
              Completed
            </div>
            <div class='ui bottom attached red basic button' v-show="!todo.done">
              Complete
            </div>
        </div>
      </template>
      
      <script type="text/javascript">
        export default {
          props: ['todos'],
        };
      </script>
      

      Let’s extract the todo template into it’s own component for cleaner code. Create a new component file Todo.vue in src/components and transfer the todo template. Our file should now look like this:

      <template>
        <div class='ui centered card'>
          <div class='content'>
              <div class='header'>
                  {{ todo.title }}
              </div>
              <div class='meta'>
                  {{ todo.project }}
              </div>
              <div class='extra content'>
                  <span class='right floated edit icon'>
                  <i class='edit icon'></i>
                </span>
              </div>
          </div>
          <div class='ui bottom attached green basic button' v-show="todo.done">
              Completed
          </div>
          <div class='ui bottom attached red basic button' v-show="!todo.done">
              Complete
          </div>
      </div>
      </template>
      
      <script type="text/javascript">
        export default {
          props: ['todo'],
        };
      </script>
      

      In the TodoList component refactor the code to render the Todo component. We will also need to change the way our todos are passed to the Todo component. We can use the v-for attribute on any components we create just like we would in any other element. The syntax will be like this: <my-component v-for="item in items" :key="item.id"></my-component>.

      Note that from 2.2.0 and above, a key is required when using v-for with components.

      An important thing to note is that this does not automatically pass the data to the component since components have their own isolated scopes. To pass the data, we have to use props.

      <my-component v-for="(item, index) in items" v-bind:item="item" v-bind:index="index">
      </my-component>
      

      Our refactored TodoList component template:

      <template>
        <div>
          <p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
          <p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
          
          <todo v-for="todo in todos" v-bind:todo="todo"></todo>
        </div>
      </template>
      
      <script type = "text/javascript" >
      
      import Todo from './Todo';
      
      export default {
        props: ['todos'],
        components: {
          Todo,
        },
      };
      </script>
      

      Let’s add a property to the Todo component class called isEditing. This will be used to determine whether the Todo is in edit mode or not. We will have an event handler on the Edit span in the template. This will trigger the showForm method when it gets clicked. This will set the isEditing property to true. Before we take a look at that, we will add a form and set conditionals to show the todo or the edit form depending on whether isEditing property is true or false. Our template should now look like this.

      <template>
        <div class='ui centered card'>
          
          <div class="content" v-show="!isEditing">
            <div class='header'>
                {{ todo.title }}
            </div>
            <div class='meta'>
                {{ todo.project }}
            </div>
            <div class='extra content'>
                <span class='right floated edit icon' v-on:click="showForm">
                <i class='edit icon'></i>
              </span>
            </div>
          </div>
          
          <div class="content" v-show="isEditing">
            <div class='ui form'>
              <div class='field'>
                <label>Title</label>
                <input type='text' v-model="todo.title" />
              </div>
              <div class='field'>
                <label>Project</label>
                <input type='text' v-model="todo.project" />
              </div>
              <div class='ui two button attached buttons'>
                <button class='ui basic blue button' v-on:click="hideForm">
                  Close X
                </button>
              </div>
            </div>
          </div>
          <div class='ui bottom attached green basic button' v-show="!isEditing &&todo.done" disabled>
              Completed
          </div>
          <div class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">
              Pending
          </div>
        </div>
      </template>
      

      In addition to the showForm method we will need to add a hideForm method to close the form when the cancel button is clicked. Let’s see what our script now looks like.

      <script>
      export default {
        props: ['todo'],
        data() {
          return {
            isEditing: false,
          };
        },
        methods: {
          showForm() {
            this.isEditing = true;
          },
          hideForm() {
            this.isEditing = false;
          },
        },
      };
      </script>
      

      Since we have bound the form values to the todo values, editing the values will immediately edit the todo. Once done, we’ll press the close button to see the updated todo.

      Let’s begin by adding an icon to delete a Todo just below the edit icon.

      <template>
        <span class='right floated edit icon' v-on:click="showForm">
          <i class='edit icon'></i>
        </span>
        /* add the trash icon in below the edit icon in the template */
        <span class='right floated trash icon' v-on:click="deleteTodo(todo)">
          <i class='trash icon'></i>
        </span>
      </template>
      

      Next, we’ll add a method to the component class to handle the icon click. This method will emit an event delete-todo to the parent TodoList Component and pass the current Todo to delete. We will add an event listener to the delete icon.

      <span class='right floated trash icon' v-on:click="deleteTodo(todo)">
      
      
      methods: {
          deleteTodo(todo) {
            this.$emit('delete-todo', todo);
          },
        },
      

      The parent component (TodoList) will need an event handler to handle the delete. Let’s define it.

      
      methods: {
          deleteTodo(todo) {
            const todoIndex = this.todos.indexOf(todo);
            this.todos.splice(todoIndex, 1);
          },
        },
      

      The deleteTodo method will be passed to the Todo component as follows.

      
      <todo v-on:delete-todo="deleteTodo" v-for="todo in todos" v-bind:todo="todo"></todo>\
      

      Once we click on the delete icon, an event will be emitted and propagated to the parent component which will then delete it.

      To create a new todo, we’ll start by creating a new component CreateTodo in src/components. This will display a button with a plus sign that will turn into a form when clicked. It should look something like this.

      <template>
        <div class='ui basic content center aligned segment'>
          <button class='ui basic button icon' v-on:click="openForm" v-show="!isCreating">
            <i class='plus icon'></i>
          </button>
          <div class='ui centered card' v-show="isCreating">
            <div class='content'>
              <div class='ui form'>
                <div class='field'>
                  <label>Title</label>
                  <input v-model="titleText" type='text' ref='title' defaultValue="" />
                </div>
                <div class='field'>
                  <label>Project</label>
                  <input type='text' ref='project' defaultValue="" />
                </div>
                <div class='ui two button attached buttons'>
                  <button class='ui basic blue button' v-on:click="sendForm()">
                    Create
                  </button>
                  <button class='ui basic red button' v-on:click="closeForm">
                    Cancel
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {
            titleText: '',
            projectText: '',
            isCreating: false,
          };
        },
        methods: {
          openForm() {
            this.isCreating = true;
          },
          closeForm() {
            this.isCreating = false;
          },
          sendForm() {
            if (this.titleText.length > 0 && this.projectText.length > 0) {
              const title = this.titleText;
              const project = this.projectText;
              this.$emit('create-todo', {
                title,
                project,
                done: false,
              });
              this.newTodoText = '';
            }
            this.isCreating = false;
          },
        },
      };
      </script>
      

      After creating the new component, we import it and add it to the components property in the component class.

      
        components: {
          TodoList,
          CreateTodo,
        },
      

      We’ll also add a method for creating new todos.

      
        methods: {
          addTodo(title) {
            this.todos.push({
              title,
              done: false,
            });
          },
        },
      

      The CreateTodo component will be invoked in the App.vue template as follows:

      <create-todo v-on:add-todo="addTodo">
      

      Finally, we’ll add a method completeTodo to the Todo component that emits an event complete-todo to the parent component when the pending button is clicked and sets the done status of the todo to true.

      
      methods: {
            completeTodo(todo) {
              this.$emit('complete-todo', todo);
            },
      }
      

      An event handler will be added to the TodoList component to process the event.

      methods: {
          completeTodo(todo) {
            const todoIndex = this.todos.indexOf(todo);
            this.todos[todoIndex].done = true;
          },
        },
      

      To pass the TodoList method to the Todo component we will add it to the Todo component invocation.

      <todo v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" v-for="todo in todos" :todo.sync="todo"></todo>
      

      We have learned how to initialize a Vue app using the Vue CLI. In addition, we learned about component structure, adding data to components, event listeners, and event handlers. We saw how to create a todo, edit it and delete it. There is a lot more to learn. We used static data in our main component. The next step is to retrieve the data from a server and update it accordingly. We are now prepared to create an interactive Vue application. Try something else on your own and see how it goes. Cheers!