66-Nodejs Course 2023: Auth: Current User
Tags: node, typescript, mongodb, fastify
Published: 2022-11-17 06:50:12

---
title: 66-Nodejs Course 2023: Auth: Current User
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1d0ixmfu5t4dzfxfk52.png
# Use a ratio of 100:42 for best results.
---

In our previous article, we'have decoupled our JWT handler, now we need to get access to current user when the token is verified, stay `focused` in this chapter as things are going to be big, `BIIIIIIIG`

## How this works

Here is how it works, first we need to define a `Road Map` to all of user types in the project, for example if the project has `Admin`, `Customer` and `Vendor`, so we've here **4 user types**, All the previous ones beside the `Guest` user type.

We'll define a map that contains all the user types and their corresponding `Model`

To do so, we need to create an `auth.ts` config file in our `src/config` directory.

```ts
import User from "app/users/models/user";
import Guest from "core/auth/models/guest";

const authConfigurations = {
  userTypes: {
    guest: Guest,
    user: User,
  },
};

export default authConfigurations;
```

The next thing we need to do is to create the `Guest` model, for now we'll make it inside the `auth/models` directory as this one won't have much to do inside it.

Let's create it quickly.

```ts
// src/core/auth/models/guest.ts
import { Model } from "core/database";

export default class Guest extends Model {
  /**
   * {@inheritDoc}
   */
  public static collectionName = "guests";
}
```

Now we defined our user types list, but don't forget to import it in our config list.

Open `src/config/index.ts` and add the following line:

```ts
// src/config/index.ts
import config from "@mongez/config";
import databaseConfigurations from "config/database";
import appConfigurations from "./app";
import authConfigurations from "./auth";
import validationConfigurations from "./validation";

config.set({
  database: databaseConfigurations,
  validation: validationConfigurations,
  app: appConfigurations,
  auth: authConfigurations,
});
```

So we imported the auth configurations in our base configurations so we can use it from the auth directory.

Now we imported the configurations but it has no use as we didn't use it in our core code, but before we use it we need to know first what is the purpose of defining these user types.

## The purpose

The purpose of defining these user types is when we verify the incoming access token from the `Authorization` header we're already have the `userType` as we are adding it (See the guest route), so when we verify the token we need to define the current user by getting the user/guest/admin/customer from the database using its defined model in the configuration and mark it as `current User`.

## Creating New Guest

We created the `Guest` model, but we didn't use it yet, so here it how we'll use it, we're going to create a new document for that guest when the `/guest` route is called and before generating the token, we'll first create a new guest document and then generate the token, that token will hold the `userType` and the `_id` of the guest document.

## A note about `_id` and `id` and when we use either one

You might get confused why i'm using `_id` most of the time and we already did create `id` with much effort? well because of two things, the first one the `_id` will always be generated so i can count on it on the low level code, which is in `core` directory, and the second one is that the `_id` field is indexed by default, so it's faster to search for it. (We didn't come to indexes yet in MongoDB 😔)

## Generating new guest

Now let's go to `registerAuthRoutes` file and import our new model `Guest` to generate a new guest document.

```ts
// src/core/auth/registerAuthRoutes.ts
import Guest from "./models/guest";
//...

export default function registerAuthRoutes() {
  //...
  // now let's add a guests route in our routes to generate a guest token to our guests.
  router.post("/guests", async (_request, response: Response) => {
    // generate a new guest first
    const guest = await Guest.create({
      userType: "guest",
    });

    // use our own jwt generator to generate a token for the guest
    const token = await jwt.generate(guest.data);

    AccessToken.create({
      token,
      // get the guest user type, id and _id
      ...guest.only(["id", "userType", "_id"]),
    });

    return response.send({
      accessToken: token,
      // return the user type
      userType: guest.get("userType"),
    });
  });

  // ...
}
```

We created first a new document of guest to generate its ids, then we generated a token using that guest data, then we saved the token in the database, and finally we returned the token and the user type.

So the token now has its own payload, which is the `userType` and the `_id` of the guest document.

## Finding By _id

We're missing something here, MongoDB requires the `_id` to be an object, so we need to convert it to an object before we use it to find the document.

So we need to update our `crud-model` first, then we need ta create a method that to find a document by its `_id`.

```ts
// src/core/database/crud-model.ts
// ...

  /**
   * Find document by the given column and value
   */
  public static async findBy<T>(
    this: ChildModel<T>,
    column: string,
    value: any,
  ): Promise<T | null> {
    const query = this.query();

    // if column is _id and value is string, convert it to ObjectId
    if (column === "_id" && typeof value === "string") {
      value = new ObjectId(value);
    }

    const result = await query.findOne({
      [column]: value,
    });

    return result ? this.self(result as ModelDocument) : null;
  }

  /**
   * List multiple documents based on the given filter
   */
  public static async list<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T[]> {
    // if filter contains _id and it is a string, convert it to ObjectId
    if (filter._id && typeof filter._id === "string") {
      filter._id = new ObjectId(filter._id);
    }

    const documents = await queryBuilder.list(this.collectionName, filter);

    return documents.map(document => this.self(document));
  }

  /**
   * Get first model for the given filter
   */
  public static async first<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T | null> {
    // if filter contains _id and it is a string, convert it to ObjectId
    if (filter._id && typeof filter._id === "string") {
      filter._id = new ObjectId(filter._id);
    }

    const result = await queryBuilder.first(this.collectionName, filter);

    return result ? this.self(result) : null;
  }

  /**
   * Get last model for the given filter
   */
  public static async last<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T | null> {
    // if filter contains _id and it is a string, convert it to ObjectId
    if (filter._id && typeof filter._id === "string") {
      filter._id = new ObjectId(filter._id);
    }

    const result = await queryBuilder.last(this.collectionName, filter);

    return result ? this.self(result) : null;
  }

  /**
   * Get latest documents
   */
  public static async latest<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T[]> {
    // if filter contains _id and it is a string, convert it to ObjectId
    if (filter._id && typeof filter._id === "string") {
      filter._id = new ObjectId(filter._id);
    }

    const documents = await queryBuilder.latest(this.collectionName, filter);

    return documents.map(document => this.self(document));
  }

  /**
   * Delete single document if the given filter is an ObjectId of mongodb
   * Otherwise, delete multiple documents based on the given filter object
   */
  public static async delete<T>(
    this: ChildModel<T>,
    filter: PrimaryIdType | Filter,
  ): Promise<number> {
    if (
      filter instanceof ObjectId ||
      typeof filter === "string" ||
      typeof filter === "number"
    ) {
      // if filter is a string and primary id column is _id, convert it to ObjectId
      if (typeof filter === "string" && this.primaryIdColumn === "_id") {
        filter = new ObjectId(filter);
      }
      return (await queryBuilder.deleteOne(this.collectionName, {
        [this.primaryIdColumn]: filter,
      }))
        ? 1
        : 0;
    }

    return await queryBuilder.delete(this.collectionName, filter);
  }
```

We added a check here in most of the methods to check if filter contains `_id` and it's a string, then we convert it to `ObjectId`.

But let's enhance it as we're duplicating the code multiple times, let's make a method to make that check.

```ts
// src/core/database/crud-model.ts
// ...

/**
 * Prepare filters
 */
protected static prepareFilters(filters: Filter = {}) {
    // if filter contains _id and it is a string, convert it to ObjectId
  if (filters._id && typeof filters._id === "string") {
    filters._id = new ObjectId(filters._id);
  }

  return filters;
}
```

Now let's update all methods to use that filter method preparing.

The entire file will look like:

```ts
// src/core/database/crud-model.ts
import { ObjectId } from "mongodb";
import queryBuilder from "../query-builder/query-builder";
import BaseModel from "./base-model";
import {
  ChildModel,
  Document,
  Filter,
  ModelDocument,
  PaginationListing,
  PrimaryIdType,
} from "./types";

export default abstract class CrudModel extends BaseModel {
  /**
   * Create a new record in the database for the current model (child class of this one)
   * and return a new instance of it with the created data and the new generated id
   */
  public static async create<T>(
    this: ChildModel<T>,
    data: Document,
  ): Promise<T> {
    const model = this.self(data); // get new instance of model

    // save the model, and generate the proper columns needed
    await model.save();

    return model;
  }

  /**
   * Update model by the given id
   */
  public static async update<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
    data: Document,
  ): Promise<T | null> {
    const model = (await this.find(id)) as any;
    // execute the update operation

    if (!model) return null;

    await model.save(data);

    return model;
  }

  /**
   * Replace the entire document for the given document id with the given new data
   */
  public static async replace<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
    data: Document,
  ): Promise<T | null> {
    const model = (await this.find(id)) as any;

    if (!model) return null;

    model.replaceWith(data);

    await model.save();

    return model;
  }

  /**
   * Restore the document from trash
   */
  public static async restore<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
  ): Promise<T | null> {
    // retrieve the document from trash collection
    const result = await queryBuilder.first(
      this.collectionName + "Trash",
      this.prepareFilters({
        [this.primaryIdColumn]: id,
      }),
    );

    if (!result) return null;

    const document = result.document;

    // otherwise, create a new model with it
    document.restoredAt = new Date();

    const model = this.self(document);

    model.markAsRestored();

    await model.save(); // save again in the same collection

    return model;
  }

  /**
   * Find and update the document for the given filter with the given data or create a new document/record
   * if filter has no matching
   */
  public static async upsert<T>(
    this: ChildModel<T>,
    filter: Filter,
    data: Document,
  ): Promise<T> {
    filter = this.prepareFilters(filter);

    let model = (await this.first(filter)) as any;

    if (!model) {
      model = this.self({ ...data, ...filter });
    } else {
      model.merge(data);
    }

    await model.save();

    return model;
  }

  /**
   * Find document by id
   */
  public static async find<T>(this: ChildModel<T>, id: PrimaryIdType) {
    return this.findBy(this.primaryIdColumn, id);
  }

  /**
   * Find document by the given column and value
   */
  public static async findBy<T>(
    this: ChildModel<T>,
    column: string,
    value: any,
  ): Promise<T | null> {
    const result = await queryBuilder.first(
      this.collectionName,
      this.prepareFilters({
        [column]: value,
      }),
    );

    return result ? this.self(result as ModelDocument) : null;
  }

  /**
   * List multiple documents based on the given filter
   */
  public static async list<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T[]> {
    const documents = await queryBuilder.list(
      this.collectionName,
      this.prepareFilters(filter),
    );

    return documents.map(document => this.self(document));
  }

  /**
   * Paginate records based on the given filter
   */
  public static async paginate<T>(
    this: ChildModel<T>,
    filter: Filter,
    page: number,
    limit: number,
  ): Promise<PaginationListing<T>> {
    filter = this.prepareFilters(filter);

    const documents = await queryBuilder.list(
      this.collectionName,
      filter,
      query => {
        query.skip((page - 1) * limit).limit(limit);
      },
    );

    const totalDocumentsOfFilter = await queryBuilder.count(
      this.collectionName,
      filter,
    );

    const result: PaginationListing<T> = {
      documents: documents.map(document => this.self(document)),
      paginationInfo: {
        limit,
        page,
        result: documents.length,
        total: totalDocumentsOfFilter,
        pages: Math.ceil(totalDocumentsOfFilter / limit),
      },
    };

    return result;
  }

  /**
   * Count total documents based on the given filter
   */
  public static async count(filter: Filter = {}) {
    return await queryBuilder.count(
      this.collectionName,
      this.prepareFilters(filter),
    );
  }

  /**
   * Get first model for the given filter
   */
  public static async first<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T | null> {
    const result = await queryBuilder.first(
      this.collectionName,
      this.prepareFilters(filter),
    );

    return result ? this.self(result) : null;
  }

  /**
   * Get last model for the given filter
   */
  public static async last<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T | null> {
    const result = await queryBuilder.last(
      this.collectionName,
      this.prepareFilters(filter),
    );

    return result ? this.self(result) : null;
  }

  /**
   * Get latest documents
   */
  public static async latest<T>(
    this: ChildModel<T>,
    filter: Filter = {},
  ): Promise<T[]> {
    const documents = await queryBuilder.latest(
      this.collectionName,
      this.prepareFilters(filter),
    );

    return documents.map(document => this.self(document));
  }

  /**
   * Delete single document if the given filter is an ObjectId of mongodb
   * Otherwise, delete multiple documents based on the given filter object
   */
  public static async delete<T>(
    this: ChildModel<T>,
    filter: PrimaryIdType | Filter,
  ): Promise<number> {
    if (
      filter instanceof ObjectId ||
      typeof filter === "string" ||
      typeof filter === "number"
    ) {
      return (await queryBuilder.deleteOne(
        this.collectionName,
        this.prepareFilters({
          [this.primaryIdColumn]: filter,
        }),
      ))
        ? 1
        : 0;
    }

    return await queryBuilder.delete(this.collectionName, filter);
  }

  /**
   * Prepare filters
   */
  protected static prepareFilters(filters: Filter = {}) {
    // if filter contains _id and it is a string, convert it to ObjectId
    if (filters._id && typeof filters._id === "string") {
      filters._id = new ObjectId(filters._id);
    }

    return filters;
  }
}
```

Now we're ready to continue, let's move on.

## Getting the current user

Now once the token is verified using `jwt.verify`, we can get the data from the `request` object from [Fastify's request object](https://github.com/fastify/fastify-jwt#usage).

After getting the payload from `request.user` property, we can get two values from it `_id` and `userType`, we'll use the `userType` value to get its corresponding model class to get an instance of that model, then we'll use the `_id` to get the document from the database.

## Auth Middleware

Before we update the token verification, let's move the code to a middleware, so this can be customized easily.

We will remove the `onRequest` event as we really don't do it, we just did it to make sure it works.

```ts
// src/core/auth/registerAuthRoutes.ts
import { Response } from "core/http/response";
import router from "core/router";
import jwt from "./jwt";
import AccessToken from "./models/access-token";
import Guest from "./models/guest";

export default function registerAuthRoutes() {
  // now let's add a guests route in our routes to generate a guest token to our guests.
  router.post("/guests", async (_request, response: Response) => {
    // generate a new guest first
    const guest = await Guest.create({
      userType: "guest",
    });

    // use our own jwt generator to generate a token for the guest
    const token = await jwt.generate(guest.data);

    AccessToken.create({
      token,
      // get the guest user type, id and _id
      ...guest.only(["id", "userType", "_id"]),
    });

    return response.send({
      accessToken: token,
      // return the user type
      userType: guest.get("userType"),
    });
  });
}
```

Now let's create `auth-middleware` file and create our own middleware.

```ts
// src/core/auth/auth-middleware.ts
import config from "@mongez/config";
import { Request } from "core/http/request";
import { Response } from "core/http/response";
import jwt from "./jwt";

export async function authMiddleware(request: Request, response: Response) {
  try {
    // use our own jwt verify to verify the token
    await jwt.verify();
    // get current user
    const user: any = request.baseRequest.user;

    // now, we need to get an instance of user using its corresponding model
    const userType = user.userType;
    // get user model class
    const UserModel = config.get(`auth.userType.${userType}`);

    // get user model instance
    const currentUser = await UserModel.findBy("_id", user._id);

    // log user data
    console.log(currentUser);
  } catch (err) {
    return response.badRequest({
      error: "Unauthorized: Invalid Access Token",
    });
  }
}
```

You can now know why we made that update, the `_id` is received as a string, so it won't be found in the collection, that's why we needed to convert it to an `ObjectId` object.

## Registering the middleware

Now let's register the middleware to `listUsers` route.

```ts
// src/app/users/routes.ts
import { authMiddleware } from "core/auth/auth-middleware";
import router from "core/router";
import login from "./controllers/auth/login";
import createUser from "./controllers/create-user";
import getUser from "./controllers/get-user";
import usersList from "./controllers/users-list";

router.get("/users", usersList, {
  middleware: [authMiddleware],
});
router.get("/users/:id", getUser);
router.post("/users", createUser);
router.post("/login", login);
```

Now open postman man, generate a `guest` token using `/guests` route, then update the `Bearer` token with the generated token, then send the request to `/users` route, you should see the logged guest model in console.

## user() function

Now we've gotten the `currentUser` model, we need to access it from anywhere in the application, so we'll create a `user()` function to get the current user.

```ts
// src/core/auth/current-user.ts
import { Model } from "core/database";

let currentUser: Model | undefined;

/**
 * Set current user
 */
export function setCurrentUser(model: Model | undefined) {
  currentUser = model;
}

/**
 * Get current user model
 */
export function user() {
  return currentUser;
}
```

We created a file that a variable called  `currentUser` and two functions `setCurrentUser` and `user` to set and get the current user.

The `currentUser` can have a `Model` or `undefined` value, and we created two functions for it to set and get the current user.

Now let's update the `authMiddleware` to set the current user.

```ts
import config from "@mongez/config";
import { Request } from "core/http/request";
import { Response } from "core/http/response";
import { setCurrentUser } from "./current-user";
import jwt from "./jwt";

export async function authMiddleware(request: Request, response: Response) {
  try {
    // use our own jwt verify to verify the token
    await jwt.verify();
    // get current user
    const user: any = request.baseRequest.user;

    // now, we need to get an instance of user using its corresponding model
    const userType = user.userType;
    // get user model class
    const UserModel = config.get(`auth.userType.${userType}`);

    // get user model instance
    const currentUser = await UserModel.findBy("_id", user._id);

    // set current user
    setCurrentUser(currentUser);
  } catch (err) {
    // unset current user
    setCurrentUser(undefined);
    return response.badRequest({
      error: "Unauthorized: Invalid Access Token",
    });
  }
}
```

We set the current user in the `try` block, and set it as undefined in the `catch` block, why? it means the user's token is invalid so we need to make sure the user is `undefined` when we use `user` function.

## Using `user()` function

Now let's head to `users-list` controller and use the `user()` function.

```ts
// src/app/users/controllers/users-list.ts
import { user } from "core/auth/current-user";
import database from "core/database";
import { Request } from "core/http/request";

export default async function usersList(request: Request) {
  const usersCollection = database.collection("users");

  // log the current user
  console.log(user());

  const users = await usersCollection.find({}).toArray();

  return {
    users,
  };
}
```

The beautiful thing is that, when we use the `usersList` controller, always `user()` function will return the current user model, why? because we won't allow accessing the controller if the token is not valid, that's why we made a return in the `catch` block in the `auth middleware` middleware.

## 🎨 Conclusion

In this chapter, we've made such an impressive progress, we created the current user and saw how to implement it and use it in our controllers, we also updated the `crud-model` to make sure it receives the `_id` properly.

## ☕♨️ Buy me a Coffee ♨️☕

If you like my articles and work, you can [buy me a coffee](https://www.paypal.com/paypalme/hassanzohdy), it will help me to keep going and keep creating more content.

## 🚀 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) to get help and support (Node Js 2023 Channel).

## 🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on [Youtube](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), the course is in Arabic language.

## 📚 Bonus Content 📚

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

* [Event Driven Architecture: A Practical Guide in Javascript](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
* [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
* [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

* [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
* [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
* [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

* [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

* [OOP In JS And TS From The Very Beginning](https://dev.to/hassanzohdy/jsts-oop-course-1-introduction-2ld)
* [ES6++: The Ultimate Guide to ES6 and Beyond](https://dev.to/hassanzohdy/es6-1-introduction-h3e)
* [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)