What we are building
We are creating a simple REST API with deno. It will be a simple CRUD for a DB collection of dogs. We will connect our deno oak application to mongo using a third party module deno_mongo.
If you are not familiar with deno please read my article. In short, It’s the new javascript/ typescript runtime by the original creator of Node. Ryan Dahl started working on deno as he wanted to overcome some of the shortcomings and missed opportunities.
Deno is very new and the ecosystem of third party software is still under development. So it goes without saying we are not going to use any of this in production or anywhere. But we should start playing around with Deno and get to understand its nuances. Dip our toes, if you will.
You will notice, we have to train our minds to put the file extensions at imports, to put additional security params before running program, top-level awaits, etc.
I will suggest you use the official VS code extension by deno. That will take care of the squiggly lines. But make sure to keep it disabled when you are not working with Deno.
Okay, Let’s get to it.
Tools used
Trying deno_mongo
To connect to MongoDB with our deno app we will be using the third-party module mentioned in the official documentation of deno, i.e, deno_mongo.
While the module is still under development and unstable, it is good enough for the demo of our CRUD.
Although deno_mongo does not have detailed documentation or guides yet at the time of writing this article, It does give us a working example. So let’s try it out.
This article assumes that you know and will not get into the steps to start a MongoDB instance locally on your machine.
Once you have the MongoDB server running on the standard port, create a file index.ts
.
index.ts
import {MongoClient} from "https://deno.land/x/mongo@v0.7.0/mod.ts";
const client = new MongoClient();
client.connectWithUri("mongodb://localhost:27017");
we will connect to a db: test
with collection: users
.
index.ts
import {MongoClient} from "https://deno.land/x/mongo@v0.7.0/mod.ts";
const client = new MongoClient();
client.connectWithUri("mongodb://localhost:27017");
const db = client.database("test");
const users = db.collection("users");
Now we will add our first user and try to print it.
index.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.7.0/mod.ts";
const client = new MongoClient();
client.connectWithUri("mongodb://localhost:27017");
const db = client.database("test");
const users = db.collection("users");
// insert (create)
const insertRes = await users.insertOne({
"name": "Ryan Dahl",
"age": 39
})
//printing respose of insert
console.log('insertRes', insertRes)
//finding all users (read)
const findAllRes = await users.find({})
console.log('findAllRes', findAllRes)
//finding one user (read)
const findRes = await users.findOne({ _id: insertRes })
/*
or :
const findRes = await users.findOnefindOne({ _id: { "$oid": "<user-id>" } })
*/
console.log('findRes', findRes)
run index.ts
with following flags :
deno run --allow-net --allow-write --allow-read --allow-plugin --unstable index.ts
this should create one user into the database, then fetch all users and then fetch one specific user. You should get a result as follows :
insertRes { $oid: "5ed1391600f90b83009a8d5d" }
findAllRes [ { _id: { $oid: "5ed1391600f90b83009a8d5d" }, name: "Ryan Dahl", age: 39 } ]
findRes { _id: { $oid: "5ed1391600f90b83009a8d5d" }, name: "Ryan Dahl", age: 39 }
that completes the C and the R’s of our CRUD. You may crosscheck the same entry in the DB console or MongoDB compass.
let’s practice more before we start coding our REST API. Let’s try insertMany
, updateOne
and deleteOne
and move on to the next section.
index.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.7.0/mod.ts";
const client = new MongoClient();
client.connectWithUri("mongodb://localhost:27017");
const db = client.database("test");
const users = db.collection("users");
// insert Many (create)
const insertRes = await users.insertMany([{
"name": "Elon Musk",
"age": 48
},
{
"name": "Jeff Bezos",
"age": 56
},
{
"name": "Mark Zuckerberg",
"age": 36
},
{
"name": "Jack Dorsey",
"age": 33
}])
//printing respose of insert
console.log('insertRes', insertRes)
//finding all users (read)
let findAllRes = await users.find({})
console.log('findAllRes', findAllRes)
//update one user
const updateRes = await users.updateOne(
{ name: "Jack Dorsey" },
{ $set: { age: 43 } },
);
//printing respose of update
console.log('updateRes', updateRes)
//delete one user
const deleteRes = await users.deleteOne({ age: 36 });
//printing respose of delete
console.log('deleteRes', deleteRes)
//finding all users (read)
findAllRes = await users.find({})
console.log('findAllRes', findAllRes)
run the same command with all the security flags and you should get following output.
insertRes [
{ $oid: "5ed142d90049a5e30081036c" },
{ $oid: "5ed142d90091da560081036d" },
{ $oid: "5ed142d90008b49d0081036f" },
{ $oid: "5ed142d900a3ca4d0081036e" }
]
findAllRes [
{ _id: { $oid: "5ed142d90049a5e30081036c" }, name: "Elon Musk", age: 48 },
{ _id: { $oid: "5ed142d90091da560081036d" }, name: "Jeff Bezos", age: 56 },
{ _id: { $oid: "5ed142d900a3ca4d0081036e" }, name: "Mark Zuckerberg", age: 36 },
{ _id: { $oid: "5ed142d90008b49d0081036f" }, name: "Jack Dorsey", age: 33 }
]
updateRes { matchedCount: 1, modifiedCount: 1, upsertedId: null }
deleteRes 1
findAllRes [
{ _id: { $oid: "5ed142d90049a5e30081036c" }, name: "Elon Musk", age: 48 },
{ _id: { $oid: "5ed142d90091da560081036d" }, name: "Jeff Bezos", age: 56 },
{ _id: { $oid: "5ed142d90008b49d0081036f" }, name: "Jack Dorsey", age: 43 }
]
Here, output of deleteOne is the number of rows deleted. In case of deleteOne the output can be either 0 or 1. You may try following example of deleteMany
for clarity.
//delete users
const deleteRes2 = await users.deleteMany({ age: {$gt : 40} });
//printing respose of delete
console.log('deleteRes2', deleteRes2)
It will print :
deleteRes2 3
findAllRes []
With U and D complete, we now have a handle on our CRUD. You may head over to docs to see more examples of deno_mongo module here.
Let’s now begin with our REST API…
Spin up our HTTP server
We will start with spinning up a deno oak server. If you are not familiar please read my article on how to create a simple HTTP server with Oak.
Here’s how the file structure of the app will look like
.
├── config
│ └── db.ts
├── router.ts
└── server.ts
create a project directory and create a server.ts
like following. Please read my comments which oversimplify what the code does.
server.ts
import { Application } from 'https://deno.land/x/oak/mod.ts';
// we will be listnning on port 8000
const port = 8000;
const app = new Application();
// this is a logger. It will log out every time we make a REST request
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
// this will log the timing of the request
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
/*
combined, it will print something like this :
GET http://localhost:8000/doggos/5ed134d6009563a400cf69e0 - 9ms
*/
console.log(`listening on port ${port}`)
app.listen({ port })
test the setup by running the following command.
deno run --allow-net server.ts
The command should run without errors and you should see
listening on port 8000
Once done continue to the next section.
Setting up router
Now we will set up the router. create a new file router.ts
And import in server.ts
as given below.
server.ts
import { Application } from 'https://deno.land/x/oak/mod.ts';
import router from "./router.ts";
const port = 8000;
const app = new Application();
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
// add these lines
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`listening on port ${port}`)
app.listen({ port })
and in router.ts
export router instance.
router.ts
import { Router } from 'https://deno.land/x/oak/mod.ts';
const router = new Router()
export default router
Restart the server and check for errors if any. Move on to DB config.
DB config
We have already had some practice with deno_mongo. In a similar way, we will connect to mongo with DB as doggos
and the collection also as doggos
. And we will export this as default as shown below in config/db.ts
db.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.7.0/mod.ts";
const client = new MongoClient();
client.connectWithUri("mongodb://localhost:27017");
const db = client.database("doggos");
const doggos = db.collection("doggos");
export default doggos;
we will now have to use all the flags we used before. Restart the server with following command.
deno run --allow-write --allow-read --allow-plugin --allow-net --allow-env --unstable server.ts
The “doggo” CRUD
We will now write the routes of our app. These will be the standard CRUD routes.
import doggos exported by config/db.ts
ctx
is the context from which we get the request
and send response
router.ts
import { Router } from 'https://deno.land/x/oak/mod.ts';
import doggos from "./config/db.ts"
const router = new Router()
// fetch all doggos
router.get('/doggos', async (ctx) => {
ctx.response.body = await doggos.find({})
})
// insert a doggo record
router.post('/doggos', async (ctx) => {
let body: any = await ctx.request.body();
const { name, age, breed } = body.value;
ctx.response.body = await doggos.insertOne({
name,
age,
breed
})
ctx.response.status = 201;
})
// fetch one doggo by id
router.get('/doggos/:id', async (ctx) => {
let id: any = ctx.params.id;
ctx.response.body = await doggos.findOne({ _id: { "$oid": id } })
ctx.response.status = 200;
})
// update doggo record
router.put('/doggos/:id', async (ctx) => {
let id: any = ctx.params.id;
let body: any = await ctx.request.body();
const { name, age, breed } = body.value;
ctx.response.body = await doggos.updateOne({ _id: { "$oid": id } }, { $set: { name, age, breed } })
ctx.response.status = 200;
})
// delete a doggo record
router.delete('/doggos/:id', async (ctx) => {
let id: any = ctx.params.id;
ctx.response.body = await doggos.deleteOne({ _id: { "$oid": id } })
ctx.response.status = 200;
})
export default router
Such 200, much wow!
POST :
http://localhost:8000/doggos/
and json body will be :
{
"name": "Max",
"age": 3,
"breed": "Shiba Inu"
}
save this "$oid" as we will need it for the next requests. for me it was 5ed2bef600bdbf7900cfdc74
GET :
http://localhost:8000/doggos/
PUT :
we will update the details of Max. Use the Id that you have for Max.
http://localhost:8000/doggos/5ed2bef600bdbf7900cfdc74
and the json body :
{
"name": "Max",
"age": 2,
"breed": "Pug"
}
DELETE :
finally, delete the record:
http://localhost:8000/doggos/5ed2bef600bdbf7900cfdc74
and it should return 1.
Conclusion & Discussion
In Summary, we have seen how we can connect our deno oak server to mongo with the use of deno_mongo. We have had some practice with using the various methods provided by deno_mongo
. We created a simple server, set up all the routes for our CRUD. We also understand that the deno_mongo
at the time of writing this article is unstable. Finally we tested our endpoints with postman.
Did you have fun playing around with Deno
? Are you excited to work with Deno professionally in future?
Top comments (0)