DEV Community

Voke
Voke

Posted on

Building in Public: CV Analyzer - Act 6 · Scene 1: Persisting the First User (Models, Database, Auth Flow)

This post documents how I implemented user registration, authentication, and database persistence using a TypeScript-based Nodejs Express backend with MongoDB, JWT, and bcrypt.

Click here for Act 5 · Scene 2

Table of Contents

Act 6 · Scene 1: Persisting the First User (Models, Database, Auth Flow)

Overview

Until now, the backend has only proven that it could start, route requests, and respond correctly. In this scene, it moves into something real. I introduce a user model, connect the server to a database, and implement the full authentication flow. That is: register, login, and logout. I verify everything end to end before touching the frontend again.

At this point, the server was stable, middleware was in place, and routes were wired correctly. The next logical step was to introduce persistence and authentication logic.

This scene focuses on connecting the backend to a database, defining a user model, and implementing the first complete authentication flow: registration, login, and logout validated entirely through Postman before shifting back to the frontend.

Let’s jump right in…

User Model

I started by defining a User model using Mongoose. The model establishes the shape, constraints, and validation rules for user data at the database level.

const UserSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      trim: true,
      required: [true, "please enter name"],
      minlength: [3, "name shouldn't be less than 3 letters"],
      maxlength: [20, "name shouldn't be maore than 20 letters"],
    },
    email: {
      type: String,
      trim: true,
      required: [true, "please enter email"],
      unique: true,
      match: [
        /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
        "Please provide a valid email",
      ],
    },
    password: {
      type: String,
      trim: true,
      required: [true, "please enter password"],
      minlength: [6, "password shouldn't be less than 6 letters"],
    },
    isRegistered: {
      type: Boolean,
      default: false,
    },
  },
  { timestamps: true }
);

export default mongoose.model("User", UserSchema);
Enter fullscreen mode Exit fullscreen mode

Database Choice and Setup

I considered SQL. But I chose MongoDB.

To me, flexibility matters more than rigid schemas. Authentication data evolves. MongoDB makes that painless without sacrificing structure.

At this stage, the database runs locally using MongoDB Compass. The server connects during startup, ensuring persistence is available before accepting requests.

Please note that the connection string will later be moved to environment variables for production readiness.

Here is the code for the connection:

    await mongoose.connect("mongodb://localhost:27017/CV-ANALYZER");
    console.log("connected to DB")
    app.listen(PORT,"localhost",()=>{
        console.log(`server listening on port ${PORT}`)
    })
Enter fullscreen mode Exit fullscreen mode

The index.ts file was where I placed it.

Registering the First User

The registration controller validates input:

if (!name || !email || !password) {
    console.log("input fields shouldn't be empty");
    return res.status(400).json({ msg: "input fields shouldn't be empty" });
  }
Enter fullscreen mode Exit fullscreen mode

Checks for existing users:

const emailExists = await User.findOne({ email: email });
    if (emailExists) {
      console.log("kindly log in");
      return res.status(401).json({ msg: "kidly log in" });
    }
Enter fullscreen mode Exit fullscreen mode

Hashes passwords using bcrypt

const salt = await bcrypt.genSalt(12);
  const hashedPassword = await bcrypt.hash(password, salt);
Enter fullscreen mode Exit fullscreen mode

Only when everything checks out does the user get written to the database.

And right there, that’s the line between “toy backend” and “real system.”

And I must state that no tokens are issued here. Registration and authentication are treated separately.

Login and Logout

The login controller verifies credentials:

if (!email || !password) {
    console.log("input fields shouldn't be empty");
    return res.status(400).json({ msg: "input fields shouldn't be empty" });
  }
Enter fullscreen mode Exit fullscreen mode

Validates password hashes:

const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      console.log("invalid credentials");
      return res.status(400).json({ msg: "invalid credentials" });
    }
Enter fullscreen mode Exit fullscreen mode

Issues a signed JWT:

      const token = jwt.sign(
      { userId: user._id, userName: user.name },
      process.env.JWT_SECRET_V!,
      { expiresIn: "1d" }
    );
Enter fullscreen mode Exit fullscreen mode

And stored as an HTTP-only cookie:

      res.cookie("token", token, {
        path: "/",
        httpOnly: true,
        expires: new Date(Date.now() + oneDay),
        // secure:true,
        // sameSite:"none"
      });
Enter fullscreen mode Exit fullscreen mode

Logout is handled by clearing the authentication cookie, immediately invalidating the session.

  res.cookie("token", "", {
      path: "/",
      httpOnly: true,
      expires: new Date(0),
      // secure:true,
      // sameSite:"none"
    });
Enter fullscreen mode Exit fullscreen mode

Validation with Postman

All authentication routes were tested using Postman:

I registered a user with this:

{
    "name":"voke",
    "email": "voke@gmail.com",
    "password": "xxxxxxx"
}
Enter fullscreen mode Exit fullscreen mode

Postman output for Register:

I logged in as a User:

{
    //"name":"voke",
    "email": "voke@gmail.com",
    "password": "xxxxxxx"
}
Enter fullscreen mode Exit fullscreen mode

Postman output for Login:

I then logged out the User:

Postman output for logout:

With all these done, I think it's safe for me to pay a visit to the frontend of this wonderful application.

Mental Model

Right now:

  • Authentication data persists correctly
  • Passwords are securely hashed
  • Tokens are issued and removed cleanly
  • The backend no longer depends on UI

Why This Scene Matters

This scene shows that I:

  • Implement authentication with security first practices
  • Validate backend behavior independently
  • Build systems ready for real users

Thanks for reading.
Let’s move on to the Next Scene.

We in the Building…
Building in Progress…

Top comments (0)