Skip to content

WebNinjaDeveloper.com

Programming Tutorials




Menu
  • Home
  • Youtube Channel
  • Official Blog
  • Nearby Places Finder
  • Direction Route Finder
  • Distance & Time Calculator
Menu

Build a Deno.js Oak CRUD REST API in Browser Using MongoDB Database in TypeScript & Test Using Postman Full Project For Beginners

Posted on October 15, 2022

Welcome folks today in this blog post we will be building a crud rest api in deno.js using oak http server and mongodb database. All the full source code of the application is shown below

 

 

Installing Deno

 

 

With Shell:

 

 

You can install the Deno.js Latest version using the below curl command

 

 

1
curl -fsSL https://deno.land/x/install/install.sh | sh

 

 

With PowerShell:

 

 

You can even install the Deno.js Latest version using the below powershell command

 

 

1
iwr https://deno.land/x/install/install.ps1 -useb | iex

 

 

After installing it you will now have deno globally installed on your machine.

 

 

 

Get Started

 

 

Installing Denon

 

 

In the root directory of your project you need to install denon package from deno using the below command

 

 

1
deno install --allow-read --allow-run --allow-write -f --unstable https://deno.land/x/denon/denon.ts

 

 

This command will make sure that denon is installed inside your project. This package will make sure that your app restarts every time whenever you make any sort of changes to app.

 

Now guys first of all see the directory structure of this Deno.js Project. It will contain all the files and folders which will be used in creating this Deno.js App.

 

 

 

 

Now first of all guys we will initialize a new .env file in which we will store the important information about the Deno.js project. It will hold the host, port number and the app domain as shown below

 

 

.env

 

 

1
2
3
APP_PORT=4500
APP_HOST="127.0.0.1"
APP_DOMAIN="http://localhost:4500/"

 

 

As you can see the Deno.js app will run on 4500 port number. And the host will be localhost and the app domain will be the full address of the app inside the browser which will be http://localhost:4500

 

 

Making the Connection to MongoDB

 

 

Before making the connection to database. We will be writing the code for the schema which is the different columns which will be there inside the mongodb database and tables.

 

 

Making the Schema

 

 

Now for making the schema you need to create a brand new folder called types inside the root directory of project and make a new file called BookSchema.ts. The code is shown below

 

 

 

 

types/BookSchema.ts

 

 

TypeScript
1
2
3
4
5
6
7
export interface BookSchema {
    _id: { $oid: string };
    title: string;
    description: string;
    author: string;
    link: string;
}

 

 

Now making the connection to the mongodb database. We need to create a brand new folder called config and inside it we need to create two files db.ts and index.ts.

 

 

 

 

db.ts

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts";
import { BookSchema } from "../types/BookSchema.ts";
 
const URI = "mongodb://127.0.0.1:27017";
const client = new MongoClient();
const dataBaseName: string = "books";
 
try {
    await client.connect(URI);
    console.log("Database successfully connected");
} catch (err) {
    console.log(err);
}
 
const dataBase = client.database(dataBaseName);
const Book = dataBase.collection<BookSchema>("book");
 
export { Bson, Book };

 

 

As you can see in the above code we are importing the MongoClient & Bson Library from the base deno.js package. And then we are importing the schema file which we created earlier in this file. And then we are setting some constant variables related to the mongodb database such as the URI & database name. And also we are initializing a new instance of MongoClient() class. And then we are using the connect() method passing the URI as an argument to connect to the MongoDB database. And then if any error takes place we are catching that error and then printing it inside the console. And then we are creating the database using the database() method and then after that using the schema defined we are creating the collection or table book. And lastly we are exporting the Bson and Book database.

 

 

And now we need to import some special libraries for this deno.js project. We can write the import statements in a separate file called deps.ts which is shown below

 

 

 

TypeScript
1
2
3
4
5
export { Application, Router, Context, send } from "https://deno.land/x/oak/mod.ts";
 
export type { RouterContext } from "https://deno.land/x/oak/mod.ts";
 
export { config } from "https://deno.land/x/dotenv/mod.ts";

 

 

And now inside the same folder of config we need to create another file called index.ts and copy paste the below code

 

config/index.ts

 

 

 

 

TypeScript
1
2
3
4
5
6
7
import { config } from "../../deps.ts";
 
const APP_PORT = config().APP_PORT;
const APP_HOST = config().APP_HOST;
const APP_DOMAIN = config().APP_DOMAIN;
 
export { APP_DOMAIN, APP_HOST, APP_PORT };

 

 

As you can see we are importing the config method to get the environment variables which we defined inside the .env file. Here in this file we are storing the host,port number and domain. And then we are exporting these variables.

 

 

Making the Controller

 

 

Now we will be making the Controller for the Deno.js Project. For this you need to make a controllers folder inside the root directory of the project. In that you need to make a file called book.controller.ts file as shown below

 

 

 

 

controllers/book.controller.ts

 

 

First of all we will be importing the required libraries for this file as shown below

 

 

TypeScript
1
2
import { RouterContext } from "../../deps.ts";
import { Book, Bson } from "../config/db.ts";

 

 

Here as you can we see we are importing the RouterContext package and Book database and Bson at the very top which is required for this controller file.

 

 

Finding All Books From MongoDB Database

 

 

First of all guys we will writing the simple controller method which will be responsible for fetching all the books which are present inside the table.

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const getBooks = async (ctx: RouterContext) => {
  try {
    // Find all books and convert them into an Array.
    const allBooks = await Book.find({}).toArray();
 
    if (allBooks) {
      ctx.response.status = 200;
      ctx.response.body = {
        success: true,
        data: allBooks,
      };
    } else {
      ctx.response.status = 500;
      ctx.response.body = {
        success: false,
        message: "Internal Server Error",
      };
    }
  } catch (err) {
    ctx.response.body = {
      success: false,
      message: err.toString(),
    };
  }
};

 

 

As you can see in the above method we are defining the method to get all the books which are present inside the table. For this we are using the find() method to fetch all records and then we are converting them to Array using the toArray() method. Then the result will be the array of objects which hold each book. We are returning a simple json response to the client. In the response we have two properties first is the status property which is equal to 200 and secondly we have the data property which holds all the book records. If any sort of internal server error takes place we are returning a 500 status code and a message of Internal server error.

 

 

Find Specific Book Info Using ID

 

 

Now we will be fetching the information of a specific book which is present inside the table using the id parameter. The code is shown below

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// @desc            Get one book
// @routes          GET /api/book
const getBook = async (ctx: RouterContext) => {
  const { id } = ctx.params;
  const book = await Book.findOne({
    _id: new Bson.ObjectId(id),
  });
 
  // If found, respond with the book.
  // If not, respond with a 404
  if (book) {
    ctx.response.status = 200;
    ctx.response.body = {
      success: true,
      data: book,
    };
  } else {
    ctx.response.status = 404;
    ctx.response.body = {
      success: false,
      message: "No book found",
    };
  }
};

 

 

As you can see inside this method we are first of all receiving the id parameter as an argument and then we are converting the id to objectid so that mongodb can understand it using the Bson library. And then we are using the findOne() method inside MongoDB to fetch only that record or book whose Id is equal to the given id. After that we are comparing in the if condition that if the book was found then we are returning that book inside the json response to the client.

 

 

Creating a new Book in MongoDB Database

 

 

Now we will be looking on how to create a new book and insert it in MongoDB Database. The code is shown below

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// @desc            Add a book
// @routes          POST /api/book
let createBook = async (ctx: RouterContext) => {
  try {
    if (!ctx.request.hasBody) {
      ctx.response.status = 400;
      ctx.response.body = {
        success: false,
        message: "No data",
      };
 
      return;
    }
 
    let body: any = await ctx.request.body();
    const { title, description, author, link } = await body.value;
    const data = await Book.insertOne({
      title: title,
      description: description,
      author: author,
      link: link,
    });
 
    console.log(body);
 
    ctx.response.body = {
      status: true,
      data: data,
    };
    ctx.response.status = 201;
  } catch (err) {
    ctx.response.body = {
      status: false,
      data: null,
    };
    ctx.response.status = 500;
    console.log(err);
  }
};

 

 

As you can see we are first of all checking the body raw json header. If no data is present then we are sending json response to the client that no data given. If some data is given then we are extracting the title,description,author and link properties of the book from the body header. And after that we are using the insertOne() method to insert a new book record inside the table passing the whole object of the book holding all the properties as an argument. And after successful insertion we are returning status code of 201 and json response to the client which contains status to true and the actual created record.

 

 

Updating the Book Record in MongoDB Database

 

 

Now we will be looking on how to update an existing book record using it’s id in mongodb database. The code is shown below

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// @desc            Get all book
// @routes          PUT /api/book/:id
const updateBook = async (ctx: RouterContext) => {
  try {
    const body = await ctx.request.body();
    const inputBook = await body.value;
    const { id } = ctx.params;
    const fetchedBook = await Book.findOne({
      _id: new Bson.ObjectId(id),
    });
 
    if (fetchedBook) {
      const { matchedCount } = await Book.updateOne(
        {
          _id: new Bson.ObjectId(id),
        },
        {
          $set: { ...inputBook },
        },
      );
 
      if (!matchedCount) {
        ctx.response.status = 404;
        ctx.response.body = {
          message: "Book does not exist",
        };
 
        return;
      }
 
      ctx.response.body = await Book.findOne(
        {
          _id: new Bson.ObjectId(id),
        },
      );
    } else {
      ctx.response.body = {
        success: false,
        body: `No book with id: ${id} found`,
      };
      ctx.response.status = 404;
    }
  } catch (error) {
    ctx.response.body = {
      success: false,
      body: error.message,
    };
    ctx.response.status = 500;
  }
};

 

 

As you can see inside this method we are receiving two arguments first is the id of the actual book which needs to be updated and secondly the object which holds all the properties of the book which will be updated. Inside this method first of all we are extracting the id from the context params. And then we are passing this id to the findOne() method to extract the book which matches the id given. After getting the actual book which needs to be updated. Secondly we are using the updateOne() method passing the object of the book to update the book. If no book is fetched using the findOne() method then we are returning the error that no book exist. If all goes well in updating the book we are returning the id of the book which is updated

 

 

Deleting the Book Using it’s ID in MongoDB Database

 

 

Now we will be deleting the book record using it’s id in mongodb database. The source code is shown below

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// @desc            Delete a book
// @routes          DELETE /api/book/:id
const deleteBook = async (ctx: RouterContext) => {
  try {
    const { id } = ctx.params;
 
    await Book.deleteOne({
      _id: new Bson.ObjectId(id),
    });
 
    ctx.response.status = 201;
    ctx.response.body = {
      success: true,
      message: "Book deleted",
    };
  } catch (err) {
    ctx.response.body = {
      success: false,
      message: err.toString(),
    };
  }
};

 

 

As you can see we are receiving the id of the book that needs to be deleted as an argument to this above method. And then we are using the deleteOne() method and in this we are passing the id and using the Bson to convert id to ObjectId. And if the book is deleted successfully then we will returning the json response to the client holding the status of 201 and message that book is deleted.

 

 

Lastly we will be exporting all the written methods using the export statement as shown below

 

 

1
export { createBook, deleteBook, getBook, getBooks, updateBook };

 

 

Full Code for Controller File

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { RouterContext } from "../../deps.ts";
import { Book, Bson } from "../config/db.ts";
 
// @desc            Get all books
// @routes          GET /api/books
const getBooks = async (ctx: RouterContext) => {
  try {
    // Find all books and convert them into an Array.
    const allBooks = await Book.find({}).toArray();
 
    if (allBooks) {
      ctx.response.status = 200;
      ctx.response.body = {
        success: true,
        data: allBooks,
      };
    } else {
      ctx.response.status = 500;
      ctx.response.body = {
        success: false,
        message: "Internal Server Error",
      };
    }
  } catch (err) {
    ctx.response.body = {
      success: false,
      message: err.toString(),
    };
  }
};
 
// @desc            Get one book
// @routes          GET /api/book
const getBook = async (ctx: RouterContext) => {
  const { id } = ctx.params;
  const book = await Book.findOne({
    _id: new Bson.ObjectId(id),
  });
 
  // If found, respond with the book.
  // If not, respond with a 404
  if (book) {
    ctx.response.status = 200;
    ctx.response.body = {
      success: true,
      data: book,
    };
  } else {
    ctx.response.status = 404;
    ctx.response.body = {
      success: false,
      message: "No book found",
    };
  }
};
 
// @desc            Add a book
// @routes          POST /api/book
let createBook = async (ctx: RouterContext) => {
  try {
    if (!ctx.request.hasBody) {
      ctx.response.status = 400;
      ctx.response.body = {
        success: false,
        message: "No data",
      };
 
      return;
    }
 
    let body: any = await ctx.request.body();
    const { title, description, author, link } = await body.value;
    const data = await Book.insertOne({
      title: title,
      description: description,
      author: author,
      link: link,
    });
 
    console.log(body);
 
    ctx.response.body = {
      status: true,
      data: data,
    };
    ctx.response.status = 201;
  } catch (err) {
    ctx.response.body = {
      status: false,
      data: null,
    };
    ctx.response.status = 500;
    console.log(err);
  }
};
 
// @desc            Get all book
// @routes          PUT /api/book/:id
const updateBook = async (ctx: RouterContext) => {
  try {
    const body = await ctx.request.body();
    const inputBook = await body.value;
    const { id } = ctx.params;
    const fetchedBook = await Book.findOne({
      _id: new Bson.ObjectId(id),
    });
 
    if (fetchedBook) {
      const { matchedCount } = await Book.updateOne(
        {
          _id: new Bson.ObjectId(id),
        },
        {
          $set: { ...inputBook },
        },
      );
 
      if (!matchedCount) {
        ctx.response.status = 404;
        ctx.response.body = {
          message: "Book does not exist",
        };
 
        return;
      }
 
      ctx.response.body = await Book.findOne(
        {
          _id: new Bson.ObjectId(id),
        },
      );
    } else {
      ctx.response.body = {
        success: false,
        body: `No book with id: ${id} found`,
      };
      ctx.response.status = 404;
    }
  } catch (error) {
    ctx.response.body = {
      success: false,
      body: error.message,
    };
    ctx.response.status = 500;
  }
};
 
// @desc            Delete a book
// @routes          DELETE /api/book/:id
const deleteBook = async (ctx: RouterContext) => {
  try {
    const { id } = ctx.params;
 
    await Book.deleteOne({
      _id: new Bson.ObjectId(id),
    });
 
    ctx.response.status = 201;
    ctx.response.body = {
      success: true,
      message: "Book deleted",
    };
  } catch (err) {
    ctx.response.body = {
      success: false,
      message: err.toString(),
    };
  }
};
 
export { createBook, deleteBook, getBook, getBooks, updateBook };

 

 

Making the Routes

 

 

Now we will be making the routes for deno.js project to map the controller methods to the routes file. For this you need to make a new folder called routes inside the root directory of the project.

 

 

 

 

routes/book.ts

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Router } from "../../deps.ts";
import {
    getBooks,
    getBook,
    createBook,
    updateBook,
    deleteBook
} from "../controllers/book.controllers.ts";
 
const router = new Router();
 
router
  .get("/api/book", getBooks)
  .get("/api/book/:id", getBook)
  .post("/api/book", createBook)
  .put("/api/book/:id", updateBook)
  .delete("/api/book/:id", deleteBook);
 
export { router };

 

 

As you can see in the above file we are importing all the methods that we have defined inside the controller file. And then we are making the actual routes that the user will use to actual interact with the mongodb database. For this we are make a new object of the Router Class. We are importing the router package at the very top. And then we are mapping the controller methods to all the routes. First of all we have the get request to /api/book to fetch all books for this we are using getBooks method. And then we are using the getBook method to get specific information of the book using it’s id. Similarly for post route we /api/book we are using the createBook method. And for put request we are updating the book using updateBook method. And then for delete request we are using the /api/book/:id passing the id as an argument and then we are calling the deleteBook method.

 

 

 

 

 

And now guys we will be making another file called index.ts file inside the routes file to make the base router importing all the routes that we defined inside the book.ts file as shown below

 

 

routes/index.ts

 

 

TypeScript
1
2
3
4
5
6
7
8
import { Router } from "../../deps.ts";
import { router as routerBook } from "./book.ts";
 
const router = new Router();
 
router.use(routerBook.routes());
 
export default router;

 

 

As you can we are importing the base Router package from the deps.ts file and then we are importing all the routes as routerBook. And then we are initializing the new Router passing the middleware of all the routes that we defined inside the book.ts in this base router. And lastly we are exporting this router using the export statement.

 

 

Making the Deno.js App

 

 

Now for starting out the Deno.js app you need to make an app.ts file inside the root directory.

 

 

 

 

app.ts

 

 

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
import { Application } from "./deps.ts";
import router from "./app/routes/index.ts";
import { APP_HOST, APP_PORT, APP_DOMAIN } from "./app/config/index.ts";
 
const app = new Application();
 
app.use(router.routes());
app.use(router.allowedMethods());
 
console.log(`Listening on: ${APP_DOMAIN}`);
 
await app.listen(`${APP_HOST}:${APP_PORT}`);

 

 

As you can see at the top we are importing all the files that we have created for database config and routing and controller files. And then we are making a new Deno.js Application using the Application() constructor. And then we are passing the middlewares which include Routes and allowedMethods() method. And lastly we are starting the Deno.js at the port number.

 

 

Running the Deno.js App

 

 

Now for running the deno.js app we need to create a denon.json file inside the root directory and copy paste the below code

 

 

 

 

denon.json

 

 

1
2
3
4
5
6
7
8
9
{
    "$schema": "https://deno.land/x/denon/schema.json",
    "env": {},
    "scripts": {
      "start": {
        "cmd": "deno run --unstable --allow-net --allow-read app.ts"
      }
    }
}

 

 

As you can see inside the above file we have defined the command inside the start section to start the deno.js app as shown below in the command

 

 

denon start

 

 

 

 

 

As you can see the Deno.js App is running on port 4500. We have used the command denon start for this purpose.

 

 

Testing the API Using Postman

 

 

Fetching all the Books

 

 

 

 

Fetching Information of Specific Book Using it’s ID

 

 

 

 

Creating a New Book Using POST Request

 

 

 

 

 

Updating the Book Using it’s ID

 

 

 

 

Deleting the Book Using it’s ID

 

 

Recent Posts

  • Android Java Project to Merge Multiple PDF Documents From Gallery Using iTextPDF Library
  • Android Java Project to Detect System Hardware & System CPU Info & Display inside TextView Widget
  • Android Java Project to Integrate Google OAuth2 Login & Logout System & Save User Info in SharedPreferences
  • Android Java Project to Export Raw Text to PDF Document Using iTextPDF Library
  • Android Java Project to Export Images From Gallery to PDF Document Using iTextPDF Library
  • Angular
  • Bunjs
  • C#
  • Deno
  • django
  • Electronjs
  • java
  • javascript
  • Koajs
  • kotlin
  • Laravel
  • meteorjs
  • Nestjs
  • Nextjs
  • Nodejs
  • PHP
  • Python
  • React
  • ReactNative
  • Svelte
  • Tutorials
  • Vuejs




©2023 WebNinjaDeveloper.com | Design: Newspaperly WordPress Theme