About Deno
Deno has just recently released version 1.0.0 and this is a new secure runtime for JavaScript and Typescript. Main key features of Deno are:
About Oak
Oak is middleware framework for Deno's http server, including a router middleware. This middleware framework is inspired by Koa and middleware router inspired by koa-router. For more Info check out here
Let's start
I'll be installing Deno using Brew.
brew install deno
To verify if the Deno is installed or not. Just type deno --version
on your terminal and it will show the install version of Deno.
$ Deno --version
deno 1.0.0
v8 8.4.300
typescript 3.9.2
After the installation steps, let's create a directory for our application
mkdir denoRestApi && cd denoRestApi
We are going to develop a Crud REST api with the following structure
- src
- controllers
- dinosaurs.ts
- models
- dinosaurs.ts
- routes
- routes.ts
- types
- types.ts
- server.ts
- Controllers : have a logic of the application and handle the client requests.
- Models : contain the model definition.
- Routes : containing API routes.
- Types : contain the types used by model and application responses.
- Server : code to run localhost server.
Now let’s create our server.ts file in the root of our directory :
import { Application } from "https://deno.land/x/oak/mod.ts";
import router from "./src/routes/routes.ts";
const port = 9000;
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
console.log(`Server running on port ${port}`);
await app.listen({ port });
The Application class wraps the serve() function from the http package. It has two methods: .use() and .listen(). Middleware is added via the .use() method and the .listen() method will start the server and start processing requests with the registered middleware.
The middleware is processed as a stack, where each middleware function can control the flow of the response. When the middleware is called, it is passed a context and reference to the "next" method in the stack.
Our Next step is to create our endpoints in our routes.ts :
import { Router } from "https://deno.land/x/oak/mod.ts";
import {
getDinosaur,
getDinosaurs,
addDinosaur,
updateDinosaur,
deleteDinosaur,
} from "../controllers/dinosaurs.ts";
const router = new Router();
router.get("/api/v1/dinosaurs", getDinosaurs)
.get("/api/v1/dinosaurs/:id", getDinosaur)
.post("/api/v1/dinosaurs", addDinosaur)
.put("/api/v1/dinosaurs/:id", updateDinosaur)
.delete("/api/v1/dinosaurs/:id", deleteDinosaur);
export default router;
One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project. See below types.ts :
export interface Dinosaur {
id: string;
name: string;
era: string;
area: string;
diet: string;
}
Properties inspired by here
Now let's create our initial list with dinosaurs in models/dinosaurs.ts :
import { Dinosaur } from "../types/types.ts";
export const Dinosaurs: Array<Dinosaur> = [
{
id: "1",
name: "Achillobator",
era: "Late Cretaceous",
area: "Mongolia",
diet: "carnivorous",
},
{
id: "2",
name: "Agilisaurus",
era: "Late Jurassic",
area: "China",
diet: "herbivorous",
},
{
id: "3",
name: "Melanorosaurus",
era: "Late Triassic",
area: "South Africa",
diet: "omnivorous",
},
];
After we have created our Dinosaur Interface, our dinosaur list and our routes, let's continue with our methods for each endpoint. controllers/dinosaurs.ts :
import { v4 } from "https://deno.land/std/uuid/mod.ts";
import {
Dinosaur,
} from "../types/types.ts";
import { Dinosaurs } from "../models/dinosaurs.ts";
const getDinosaurs = ({ response }: { response: any }) => {
response.body = {
success: true,
data: Dinosaurs,
};
};
const getDinosaur = (
{ params, response }: { params: { id: string }; response: any },
) => {
const selectedDino: Dinosaur | undefined = Dinosaurs.find((dino) =>
dino.id === params.id
);
if (selectedDino) {
response.status = 200;
response.body = {
success: true,
data: selectedDino,
};
} else {
response.status = 404;
response.body = {
success: false,
msg: "Dinosaur Not Found",
};
}
};
const addDinosaur = async (
{ request, response }: { request: any; response: any },
) => {
if (!request.hasBody) {
response.status = 400;
response.body = {
success: false,
msg: "No data",
};
} else {
const { value : dinosaurBody } = await request.body();
const dinosaur: Dinosaur = dinosaurBody;
dinosaur.id = v4.generate();
Dinosaurs.push(dinosaur);
response.status = 201;
response.body = {
success: true,
data: dinosaur,
};
}
};
const deleteDinosaur = (
{ params, response }: { params: { id: string }; response: any },
) => {
const filteredDinosaurs: Array<Dinosaur> = Dinosaurs.filter(
(dinosaur: Dinosaur) => (dinosaur.id !== params.id),
);
if (filteredDinosaurs.length === Dinosaurs.length) {
response.status = 404;
response.body = {
success: false,
msg: "Not found",
};
} else {
Dinosaurs.splice(0, Dinosaurs.length);
Dinosaurs.push(...filteredDinosaurs);
response.status = 200;
response.body = {
success: true,
msg: `Dinosaur with id ${params.id} has been deleted`,
};
}
};
const updateDinosaur = async (
{ params, request, response }: {
params: { id: string };
request: any;
response: any;
},
) => {
const requestedDinosaur: Dinosaur | undefined = Dinosaurs.find(
(dinosaur: Dinosaur) => dinosaur.id === params.id,
);
if (requestedDinosaur) {
const { value : updatedDinosaurBody } = await request.body();
const updatedDinosaurs: Array<Dinosaur> = Dinosaurs.map(
(dinosaur: Dinosaur) => {
if (dinosaur.id === params.id) {
return {
...dinosaur,
...updatedDinosaurBody,
};
} else {
return dinosaur;
}
},
);
Dinosaurs.splice(0, Dinosaurs.length);
Dinosaurs.push(...updatedDinosaurs);
response.status = 200;
response.body = {
success: true,
msg: `Dinosaur id ${params.id} updated`,
};
} else {
response.status = 404;
response.body = {
success: false,
msg: `Not Found`,
};
}
};
export {
updateDinosaur,
deleteDinosaur,
getDinosaurs,
getDinosaur,
addDinosaur,
};
Run application
Deno run --allow-net server.ts
Request with curl
Resolution
We've created a dead simple, readable rest api with few lines of code. If you noticed we did not used any Node_modules dependancies instead Deno has an amazing list of features at Standard Library and Third-Party Modules. I like Deno so far and I am very excited about the out-of-box tools it provides.
You can find my repo here. Leave a 🌟 if you liked it.
Thanks a lot,
Don't hesitate to write any comments below, I would love to answer.
Feel free to connect on:
Top comments (3)
A few things I learned from Koa (from which Oak is inspired):
response.body
You can set a middleware to handle error http codes, if you want to send a JSON payload along with the status, without specifying the error payload in each of your routes:
All that should simplify the code of your router handlers a fair bit :)
I created middleware for uploading files:
deno.land/x/upload_middleware_for_...
awesome !