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
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
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
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
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
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.
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
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
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
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
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
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
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
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
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