Nextjs 13 Tutorial, Todo App (2024)

In here we want to practice actual code writing skills, This is a Nextjs 13 Tutorial, do so with the very versatile popular tech stack of nextjs (v13, app folder approach) and Mongodb with mongoose in the back and React to handle the view and rendering of our basic To Do App. We will not do much of design or SASS or CSS. We will not build a backend API server but we’ll use the Nextjs routes handling In this nextjs tutorial to create a basic CRUD API for create, read, update and delete an item from our to do list. We will not do authentication right now but it’s on the table and we might do it in our next post!

Before you continue, consider reading “First Day Engineer, Install everything you need” – to get a fully work station of a profesional software engineer with all the tools you’ll need.

Tech Stack

TechnologyLevelNote
nodejs – better to install with NVM(!)
BeginnerTo write the basic API, routes, connection to database, handle data
HTML, Reactjs, JSXVery BeginnerHTML, JSX, Reactjs will help us to show the user the information from the database and design it how we want to structure the information. ReactJs will help us to design the code, structure components, and give our project a scalable approach so we can add more features and increase the complexity of our UI.
CSS,SASSWe Will not do much CSS or SASS or any design here, in some cases in some tutorials we will be using existing UI library but here we will not do any design (so the project will look ugly, prefer yourself).
MongoDB, MongooseBeginnerMongoDB is our NOSQL, large scale database to manage easily our todo data structure, it also useful because it’s JSON structure of data which makes it easier for us to write and read data. we’ll be using Mongoose as well to strictly structure our database object and define the data, because we don’t want anyone(other engineers, or ourself) to inject inappropriate or corrupted data.
NextJSBeginnerNextjs is soft-framework, it will provide us with structure for our files, our folders and an opinionated approach to backend(API) structure to implement. it is good because we don’t want to dive into complex software engineering concepts like MVC or micro services and starts diving into Devops work by managing more than one server. our project is small hence we’ll be using Nextjs – but thanks to Nextjs it can scalable and we’ll take from Nextjs what we want – which makes Nextjs a Wonderful soft-framework and easy to understand in this nextJS tutorial.
Github, VScode, Browser, ChatGPT, Terminal, MongoDB Compass, curl(terminal)BeginnerThose tools are behind the scenes, we might not write or read about them in this article but they are here, in used. However you can pick whatever you want here and it’s up to you.
Above will give you idea behind our decision making and what each of our tech stack responsibilities are meant to handle.

Start Here – Nextjs Tutorial

to begin our project we’ll use NPX(npx comes with nodejs, it help us execute code from a remote package and run it immediately, to start we run the project:

npx create-next-app@latest --ts

Above command we’ll run in the computer terminal which will prompt a few questions like the name of our project and other questions (please pick “app” folder approach).

Inside the project folder create a file called “.env.local” that file will contain sensitive information about our Database and other information, here’s how he content of the file should look like:

DATABASE_URL=mongodb://localhost:27017/next_todo_list
API_URL=http://localhost:3000/api/todos

Now, Run the following command line inside the folder:

npm run dev

At this point we should have a running nextjs page at http://localhost:3000

First let’s define our todo data structure with the help of typescript, We’ll create a “type.ts” inside folder of “utils” and define our todo data structure and we’ll use it along our nextjs 13 tutorial:

// <root>/utils/type.ts
export interface Todo {
    _id?: number,
    item: string,
    completed: boolean
}

Step 1: Database Connection

To manage our interaction with the MongoDB, we’ll be using mongoose. Mongoose will provide us few things: a way to connect to the database and maintain that connection during our CRUD actions from our Todo API that we’ll write in Nodejs. And Mongoose will provide structure to our data so we’ll not inject whatever random, partial data into our data base and corrupt our data.

npm install mongoose

Create a folder called “utils” in the root of our project an a file called “mongoose.ts”, that file will be responsible to manage our database connection and we’ll call that file from our API files:

// utils/managoose.ts
import mongoose from 'mongoose';
const connectMongoDB = async () => {

  if (!process.env.MONGODB_URI) {
  throw new Error('Invalid environment variable: "MONGODB_URI"')
}

    try {
        await mongoose.connect(process.env.MONGODB_URI);
        console.log('Connected to MongoDB Via Mongoose');
    } catch (error) {
        console.log('ERROR DURING CONNECTION:', error)
    }
}

mongoose.connection.on('connected', () => {
  console.log('Connected to MongoDB');
});

mongoose.connection.on('error', (err) => {
  console.error('Connection error:', err);
});

export default connectMongoDB;

Step 2: Data structure and Types

Create a folder call “models” those models are simply definition of future data structure and how we want data to look like, this is where we need to be strict, those models will be used in our API to CRUD our data, and it will have limitations that will protect our data from corrupted data or security issues. inside the folder, let’s create a file call “todos.ts”

// models/todos.ts
import mongoose, { Schema } from 'mongoose';


const schema = new Schema( {
    _id: String,
    item: String,
    completed: Boolean
})


const TodosModel = mongoose.models.Todos || mongoose.model('Todos', schema);

export default TodosModel;

Step 3: Create Todo API Nextjs

We have Database connection, We have database connection, we have data model structure, now we want to create CRUD API to interact with MongoDB. With the help of nextjs we’ll create a folder as such

  • app/api/todos – in here we will create a file call “route.ts” that file is name is a “reserved word” in the Nextjs soft-framework world, It means it serve a strict purpose which is “to contain, manage and handle routes of API between the view(react) and the Mongodb database”.

Let’s write all the initial functions(empty ones at that moment) of our CRUD for our Todo App, we’ll also import all the dependencies at the top. Let’s dive in at late write each and every API and also test all of them with “curl” in the terminal to see they are working (those are crucial and important “working technique that will sped up your learning process)

// <root>app/api/todos/route.ts
import clientPromise from '../../../utils/mongoose';
import TodosModel from '../../../models/todos';
import { NextResponse } from 'next/server';
import {ObjectId} from 'mongodb';

interface TodoReqInterface {
    item: String,
    completed: boolean
}

/**
 * Create new todos in our system, currently without a auth system, any post create system and without limitation too
 * @param req 
 * @returns 
 */
export async function POST(req: Request) {

}

type Todos = {} | null;


/**
 * this route will retrieve todos, if we have a todoId we'll return a single one, if we have none, we'll return all of them (with limit to the last 5 to avoid problems)
 * in real world we always put a lot of limitations on pulling data from database, to avoid someone getting all of our data, accessing sensitive data and much more
 * @param req 
 * @returns 
 */
export async function GET(req: Request) {

}

/**
 * This route will allow the client (front end) to delete a todo, here too in a real world we'll limit who capable to do this delegation
 * @param req 
 * @returns 
 */
export async function DELETE(req: Request) {

}

/**
 * This is how we update our todos when it's completed for example, or change in title
 * @param req
 */
export async function PUT(req: Request) {


}

Feel free to read the comments, some of them more advance topics so you can skip them for now. However we are here to learn and learn as much as we possibly can. Let’s dive in the first few methods above and write them down and test them down.

Step 4: CRUD Methods

Write To Database
Let’s write the first method, which is creating a new Todo, we’ll also try to test it with Curl in the terminal and MongoDB Compass:

export async function POST(req: Request) {
    console.log('Post Request')
    await clientPromise();

    const { item, completed }:TodoReqInterface = await req.json(); // here we read the incoming request (from http://localhost:3000/todo or from a curl)
    console.log('creating new todo with item:', item, 'completed:', completed);

    const newTodo = {
        _id: new ObjectId(),
        item: item,
        completed: completed
    }

    console.log('new todo data object:', newTodo);
    try { // here we try to add the data to the database, it might fail so we use try catch
        const data = await TodosModel.create(newTodo); // here we use TodosModel and "create" method to inject the object into Mongodb
    } catch(e){
        return NextResponse.json({data: {}, status: 500, description: 'ERROR trying to create a new to do'}) // we return error in case of a problem. 
    }

    return NextResponse.json({data: {}, status: 201, description: 'Successfully SAVED todos!'}) // we retturn sucess msg in case of sucess
}

Above is the function with few comments inside, and console logs(which should not exists in production grade code but here we are testing and learning so they are crucial and important). In above code we:

  1. connect to the database with clientPromise (via mongoose)
  2. we read the request and pharse it into a JSON
  3. we structure “newTodo” with new incoming information
  4. we try to inject the object into MongoDB via mongoose and mongoose model called “TodosModel” as we build and descibe eailer. it provides us with “create” method.
  5. we (must) return a response via Nextjs “NextResponse” (which simply extent the browser native “Response” method with few more features.

Test Above Code

I’ll do it once, you’ll need to do it for each of below methods (delete, read, update/put). we’ll try to write a single todo into our database with CURL via the terminal ( like hackers 😀 ) and we’ll use MongoDB Compass to see the new Todo via dashboard.

Let’s start with terminal command of CURL:

curl -X POST http://localhost:3000/api/todos \ \n -H "Content-Type: application/json" \ \n -d '{"item": "Put your life into order.", "completed": 0}'
Above show the testing we do via CURL and afterwards with Mongodb Compass to see that the data is injected into the Database.

Above terminal command line contain very important characters like “\” which split between options in the command and “\n” which create a new lines for certain parts of the command (otherwise CURL won’t work). Another option to test this create method is to use Postman or other similar tool.

Read From Database
This is the “get” and read of todos from our database, there are planty of different approach to do so, since our application in minimal and simple we’ll do 2 “business logic in here” the first one is to get ALL todos (we’ll limit to 5 to avoid problems) and another BL is to return a specific, single todo (base on TodoId)

export async function GET(req: Request) {
    await clientPromise();
    const { searchParams } = new URL(req.url);
    console.log('searchParams:', searchParams); // we use thise console log to see that the url does pass params that we need. like "TodoId"

    const todoId = searchParams.get('todoId'); // in real world case we would filter, analyze, and carefully check what this todo id is all about due to security
    // in addition we would want this to be protected, maybe this user is not authenticated to see this specific todo? but this one is relevant in bigger systems.

    let getTodos: Todos = {};

    if(todoId) { // if we have request for specific todo let's get just one
        getTodos = await TodosModel.findById(todoId).limit(1)//.catch((e) => console.log('ERROR: while trying to get todos from todos model'));
    } else {
        // otherwise return all
        getTodos = await TodosModel.find({}).limit(5)//.catch((e) => console.log('ERROR: while trying to get todos from todos model'));
    }

    console.log("get all the todos:", getTodos)
    return NextResponse.json({data: getTodos, status: '201', description: 'retrive todos'})

}

In above code we do as such:

  1. Connect to databse (this is a simple approach, large scale app will have this connection done in a middleware!)
  2. We read the Query params in the URL via “searchParams”.
  3. We get the “TodoId” from the url, which shoud look like this: http://localhost:3000/api/todos?TodoId=<long mongodb hash id>
  4. If we have one, we return a single todo, if we don’t have TodoId we’ll return all of them (limit to 5 for safety)
  5. As previously, we return, always, response via NextResponse (or regular Response object) – but we must return response.

to test above code we’ll run curl:

for single todo:
curl http://localhost:3000/api/todos\?<mongoDb Id get it from compass or from below command line>
for all todo:
curl http://localhost:3000/api/todos

in the terminal we should get all Todos or single one (we can use the “get all” to fetch an ID then use it to fetch a single todo.

Delete From Database
Now we want to allow our consumer of our API (ourself in this case) to delete a todo, we’ll write the code without and security or safety, because it’s a simple application (however in the future we’ll want to verify who try to delete the todo, whether if they should be able to do so or if they have the authorisation to do so)

export async function DELETE(req: Request) {
    await clientPromise();

    const { searchParams } = new URL(req.url);
    const todoId = searchParams.get('todoId');

    // todo: put protected route, so not EVERYONE can delete this TODO! but only admin or only specific authenticated user!!
    console.log('delete the following todo with the following ID:', todoId);

    const deleteTodo = await TodosModel.findByIdAndDelete(todoId).catch((e) => console.log('error trying delete a TODO:', e))

    console.log('deleteTodo: ' , deleteTodo)
    return NextResponse.json({data: '', status: '200', description: 'successfully deleted a todo'});
}

In above code we:

  1. Get the relevant Todo id as we did before with SearchParams (we want to delete a specific todo, not the entire database!)
  2. We will use “TodosModel.findByIdAndDelete” to delete one item.
  3. We return response

It possibility to wrap the deletion process with try and catch so we can detect any problems (like wrong ID etc).

Update Database
Below we’ll do an update, for example changing the todo from “in process” or “not done” to “done” or “completed”. Let’s take a look:

export async function PUT(req: Request) {
    await clientPromise(); // in bigger system we'll have this db connection done ONCE and kept in memeory.
    console.log("trying to update a todo in the list");
    // id come from URL, actual data of todo comes from post("headers")
    const { item, completed }:TodoReqInterface = await req.json();

    const { searchParams } = new URL(req.url);
    const todoId = searchParams.get("todoId"); // this todo id will come from URL as such http://.../api/todos?todoId=XXX

    console.log("updating todo id:",todoId);
    try {
        // it didnt work the first time because we forgot about the await!
        const res = await TodosModel.findByIdAndUpdate(todoId, {item, completed}, { new: true });
        console.log("res of findByIdAndUpdate:", res)
    } catch(e) {
        console.log("fail update a todo:", e)
    }
    
    return NextResponse.json(({data: "", status: "200", description: "sucessfully update a todo in the database"}))
}

Above we do as such:

  1. Get the Todo id of which we want to apply the update
  2. We’ll use mongoose utility “TodosModel.findByIdAndUpdate” to perform the update.
  3. We use the ID to target the relevant Todo and structure the new data into the object and perform the “PU”
  4. We apply ” { new: true } ” so the return result of the PUT will be the new object (rather than the “current” or “previous” or “old” object)
  5. we of course return a response like always (error or sucess, always return reposnse!)

Step 5: View Home Page

View All Todos
This part we’ll print all the list of “todos” in our database. We’ll write basic HTML and avoid SASS and CSS at that moment (it will look ugly, prepare yourself!), but our goal now is to print the database information and list of “todos” into a page HTML in a dynamic manner. We’ll use Reactjs, HTML, and JSX for that purpose:

// <root>/app/page.tsx (basic existing page of nextjs project, you should delete the existing content of the page.
import { Todo } from '../utils/types';
import Link from "next/link"
import styles from "./page.module.css";

// Our nextjs 13 tutorial contain the approach of nextjs 13 of server side http requests as such:
// 1. async function at the top of the component:
async function GetTodos() {
  let data = await fetch('http://localhost:3000/api/todos')
  return data
}

interface TodosData {
  data: Todo[]
}

export default async function Home() {
  // 2. async http request inside the page component (this request will be done in the server side)
  let todosData:TodosData = await (await GetTodos()).json();

  return (
    <main className={styles.main}>
        <h1>Todo App</h1>
        <h2>Pick a todo to see more</h2>

        {todosData.data.map(t => (
          <div key={t._id}>
            <Link href={`/todo/${t._id}`}>
              <h3 style={{cursor: 'pointer'}}>{t.item} - {t.completed ? 'completed' : 'incomplete'}</h3>
            </Link>

          </div>
        ))}
    </main>
  );
}

What we do in above code?

  1. We import dependencies like types and Link component from nextJs (it will help us transition to a secondary page we’ll build).
  2. We use nextjs 13 approach and do a async server side http request with a function that call “GetTodos”.
  3. We parse the async request to a json and run a “map” function in javascript on the array of the todos to print all the todos using JSX and Reactjs.
  4. We use <link> component in Nextjs to route the user to the next page (which is simply to see the single “todo” in a single page.

Step 6: Complete And Delete Task

At this point we should be able and click one of the “todos” in the list but the page will return 404, because we need to build it – let’s do it and continue our Nextjs 13 tutorial. More important is to learn the difference between Server side and Client side in nextjs 13, and this is why we do this tutorial, specificlly for this part, which could be the more complicated and difficult to distinguish between the two sides and responsibilities

Server Singe To Do View
Here we print static data, and perform server side http request, Which means that anything here should be server side only! it means that we cannot perform or use “useState” or “useRouter” or perform fetch that are client side here! this is why we’ll create another component that called “TodoActions.tsx” (up next). But now let’s write the server side of our “to do”:

// <root>/app/todo/[todoId]/pages.tsx
// This file is server side rendered! which is why we'll create another file that is front end, for client side actions "TodoActions.tsx":
import { Todo } from "../../../utils/types";
import TodoActions from "./TodoActions";

interface FetchTodoResponse {
    data: Todo
    status: number
    description: string
}

interface TodoProps {
    todo: Todo
    params: { todoId: string}
}

async function GetSingleTodo(todoId: string) {
    console.log("fetch to do with todo id:", todoId)
    const data = await fetch("http://localhost:3000/api/todos?todoId=" + todoId);
    return data;
}
  
export default async function Page(props: TodoProps) {
    let todoData:FetchTodoResponse = await (await GetSingleTodo(props.params.todoId)).json();
    console.log("todoData:" , todoData); // data coming from server side, from api and is fetch server side as well!

    const todo = todoData.data;

    return (
        <div>
            <h1>Title: {todo.item}</h1>
            {/** Since actions like delete, update are done in front end, we split page.tsx as server file and TodoActions.tsx as a front end file: */}
            <TodoActions todo={todo}/>
        </div>
    )
}

What we do in the above code?

  1. First it’s a matter of responsibility, here we do backend (of the front end) code only. Which means bunch of code we might things should exists here, won’t be able to run! and even the console logs will be output to the terminal, since this “page.tsx” (as provided us by Nextjs 13 and we learn in this tutorial) is a server side file.
  2. We start by importing all dependencies, you’ll, no front end or client side stuff and code here!
  3. We define some types.
  4. We perform server side fetch to get a single to do base on “todoId” (with func: GetSingleTodo)
  5. Than we render the static data, which in our case is the title, and later we pass the “Todo” data to the client side component called “TodoActions”.

The separation here is important, you’ll face various errors and problems if this part won’t sink in. You’ll hae a bad time here if you don’t seek the differences between the two (read the comments)!

Client Action To do
In the client side component, This is the “real front end” you’ll find userRouter (react server side router manager), useState and browser/client side fetch to do deletion and completion of a “Todo”, Let’s take a look:

"use client"
// <root>/app/todo/[todoId]/Todo.tsx
// we split page.tsx into Todo.tsx because "page" will be our server side rendring and "Todo.tsx" will be our front end/client rendering
import { useState } from "react"; // this component won't work inside "pages.tsx" since parent is server side, not client side
import { Todo } from "../../../utils/types";
import { useRouter } from "next/navigation";

interface TodoProps {
    todo: Todo
}

export default function TodoActions(props: TodoProps) {
    // we split responsibility, parent did the fetch in server side:
    console.log("todo from props" , props.todo)

    const [todo, setTodo] = useState<Todo>(props.todo);

    const router = useRouter();

    // part of our CRUD, this one if the "PUT" (AKA update)
    const handleComplete = async () => {
        // in case the todo in not completed yet, lets makret it as such since the user clicked on "complete"
        if(!todo.completed) {
            const newTodo: Todo = {...todo, completed: true}

            // before we continue to set todo, the next we want to wait for fetch to finish
            await fetch("http://localhost:3000/api/todos?todoId=" + todo._id, {
                method: 'PUT',
                headers: {
                    "Content-type": "application/json"
                },
                body: JSON.stringify(newTodo)
            });
            setTodo(newTodo);
        }
    }

    // part of our CRUD, this part is the "delete"
    const handleDelete = async() => {
        // in real world application the url will come from the ENV variable, not hard coded!
        await fetch("http://localhost:3000/api/todos?todoId=" + todo._id, {
            method: "DELETE"
        });

        // since this todo is deleted we want to mvoe the user to an existing page
        router.push("/");
    }

    return (
        <div>
            <h2>{todo.completed ? "completed" : "incompleted"}</h2>
            <button onClick={handleComplete}>Complete Item</button>
            <button onClick={handleDelete}>Delete Item</button>
            <button onClick={() => {
                console.log("back")
                // for user expierence we want to allow user to go back to the list of todos
                router.push("/");
            }}>Back</button>
        </div>
    )
}

in above code we do the following:

  1. We declare this component to be “use client” – which means from this point and on regards to rendering and actions. Everything will be expected to be done in the browser, in the client side of things. Hence, no server side rendering, hence here we use “useState” here we use browser router and more.
  2. Since it’s client side(browser) component, We use nextjs 13 navigation (not router, Which is the previous approach to managing routes in nextjs. Next 13 navigation simply provide more feature for client side behaviour and useful hooks and Link components to manage the browser URL.
  3. We create 2 handle functions, those are functions that handle the client side (browser, user actions) once the user “click” or interact with the UI. One for completion of task/item and one for deletion of task.
  4. Another action that is done here is the inline function for “Back” with the browser router of Nextjs 13.

The separation between page.tsx is important, it’s important to understand which code you can write in the page.tsx (which function here as a backend) and which actions you cannot write in the page.tsx and as well the “TodoActions.tsx” (I’ll recommend to repeat this step).

Step 7: Create Task

Here we want to continue practice and learn the differences between server side and client side in nextjs 13 tutorial, Here we’ll create once again page.tsx (remember it’s a reserve word in nextjs 13) inside a folder called “create” and we’ll create a “Create.tsx” file that will function as our client side component.

Before we create those files, Let’s add a route link component from the homepage to the new file that we’ll create next:

// <root>/app/page.tsx
export default async function Home() {
  let todosData:TodosData = await (await GetTodos()).json();

  return (
    <main className={styles.main}>
        <h1>Todo App</h1>
        <Link href="/todo/create"><button>Create a New Todo</button></Link> {/** <----- new line */}

        {todosData.data.map(t => (
          <div key={t._id}>
            <Link href={`/todo/${t._id}`}>
              <h3 style={{cursor: 'pointer'}}>{t.item} - {t.completed ? 'completed' : 'incomplete'}</h3>
            </Link>

          </div>
        ))}
    </main>
  );
}

Now let’s create the main page of the create page:

// <root>/app/todo/create/page.tsx - server side render component 
import CreateTask from './create';

export default function page() {
    return (
        <div>
            <h1>Add New Task</h1>
            <CreateTask />
        </div>
    )
}

Above code is very lean, it simply inside a folder called “create” under “todo” folder, The reason is that this way we “build the routes” of our application, We build our application URL’s by building folders and files inside our application structure, It is a topic I’ve discussed in 2018 in one of the startup I work for called Fiverr – building large scale web application. Right now the page.tsx of “create” folder/url is small, but in the future we’ll be able to add more features, It renders in the server side, it is static but can be (server side) dynamic in the future.

"use client"
// <root>/app/todo/create/create.tsx
import { useRouter } from 'next/navigation'
import { FormEventHandler, useRef } from "react";
import { Todo } from "../../../utils/types";

export default function CreateTask() {
    const router = useRouter();

    const item = useRef<HTMLInputElement>(null);

    const handleSubmit: FormEventHandler<HTMLFormElement> = async event => {
        event.preventDefault();
        let todo: Todo = { item: "", completed: false}

        if(null !== item.current) {
            todo = { item: item.current.value, completed: false}
        }

        await fetch("http://localhost:3000/api/todos", {
            method: "post",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(todo)
        })

        router.push("/")
    }


    return (
        <form onSubmit={handleSubmit}>
            <input type="text" ref={item} />
            <input type="submit" value="Add Task" />
        </form>
    )
}

Let’s go over the above code, some of it will look familiar:

  1. We use the router since this application define as “use client” and is for the browser to render.
  2. We use React’s hook called “useRef” for “two way data binding”, To attach an input by a user to a state of data that will be used for us later (once we click “submit”).
  3. We arie “handleSubmit” method that meant to handle the submission of a new task to our database.

At this point you should be able to add a new task to your. todo list via the UI as well! But again in real world application we’ll have authentication, protection, filter of data and more.

Summary

Hold on! This application is half baked, It’s not finished and it has bugs. Our goal in this tutorial is to allow you to study and increase your code writing skills, If we give you all the solutions you won’t be able to learn, This is why this application is half baked, You’ll need to continue. Here are some tasks that needs to be done:

TitleDescriptionStack Holder
Data Is Not SyncBug: When a client side action is done, and the “back” button is pressed – the data is not sync with the data base (story: user adds task, user click “back” when user transition to previous page it cannot see the new task).Engineer
Research for React UI Library and apply to our “To do” ApplicationApplication looks visually ugly – consider using SASS or CSS or a UI library to make it prettier, With predefine design in a UI library the application can look much more professional.Senior Engineer, Infra Team, Tech Lead
Use Env vars to define API URL’sURL’s are hardcoded, it’s a more of an infrastructure team job to avoid this, since in different environment the domain will be different (in production vs locally the URL of our API might be different) – try to find ways to fix it!Infra Team, Tech Lead, Architect
Add Authentication with Next AuthAdd authentication via “oAuth” approach (e.g google authentication button), and add protection to the routesSenior Engineer, Infra Team, Teach Lead
You should come up with the next taskBecome the product manager of your own projects.Your future self
Stack Holder is just general estimation, You cant do the task as well.

Keep writing code, Practicing your code writing skills and come up with new projects, You should also be familiar with different libraries and framework in the javascript world and you are welcome to read some of my other publication and interesting projects I publish like “How to Deploy a Project To Digital Ocean“.

Leave a Reply

Your email address will not be published. Required fields are marked *

All rights reserved 2024 ©