Photo by Sincerely Media on Unsplash
How to build a REST API with TypeScript using Deno, Opine, and DenoDB.
I started to learn Typescript these days and I felt interested in Deno, "Deno is a simple, modern, and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.". I was curious about how to build a REST API with it. And the framework that I really liked was Opine, "Fast, minimalist web framework for Deno ported from ExpressJS." according to its docs.
In this tutorial, we are going to build a REST API using Deno, Opine as a web framework, and DenoDB as an ORM.
We are going to start defining our model. I going to use the same model from DenoDB tutorial.
Requirements:
- Deno installed in your machine (You can install it from here).
- Knowledge about basic concepts of OOP(Classes, methods, instances).
-----OpineApi
|------model
| |-----Flight.ts
|------router
| |-----Routers.ts
|------app.ts
|------db.ts
We create a model folder in our API directory and create a model file.
Flight.ts
import { DataTypes, Model} from 'https://deno.land/x/denodb/mod.ts';
export class Flight extends Model {
static table = 'flights';
static timestamps = true;
static fields = {
id: { primaryKey: true, autoIncrement: true },
departure: DataTypes.STRING,
destination: DataTypes.STRING,
flightDuration: DataTypes.FLOAT,
};
static defaults = {
flightDuration: 2.5,
};
}
In Flight.ts we need to import Datatypes and Model classes from DenoDB and then create a Flight class, that inherits the properties and methods from the Model class. Then we named our table "flights" and mark timestamps as true, this will show when we create a row and when we update it.
Then we define the fields of our model and its data types, in this case: id, departure, destination, and flight duration, if we don't specify the flight duration, it will show 2.5 as default.
After that, we create a file in our API directory where we going to connect the database. I going to use SQLite, but DenoDB has support for PostgreSQL, MySQL, MariaDB, and MongoDB.
db.ts
import { Database, SQLite3Connector } from 'https://deno.land/x/denodb/mod.ts';
//@ts-ignore
import {Flight} from './model/Flight.ts';
const connector = new SQLite3Connector({
filepath: './database.sqlite',
});
export const db = new Database(connector);
db.link([Flight])
export default db
We need to import Database and SQLite3Connector from Deno Db and Flight class from the model file.
We create an immutable variable to store an instance of the SQLite connector and specify the path of the SQL file. Then we create an immutable variable to store an instance of a database class, connect our database and after that, we link it to the model.
Now we create a router folder in the API directory and create a file to program the routers.
Routers.ts
import { opine, json } from "https://deno.land/x/opine@2.1.5/mod.ts";
import {Flight} from '../model/Flight.ts';
export const appFlight = opine();
appFlight.get("/flight", async function(req, res) {
try {
const flights = await Flight.all()
res.json(flights)
} catch(err) {
console.log(err)
res.setStatus(500).send({error: 'Something went wrong'})
}
});
Our first router is to use to get all the flights in the database. We need to create an opine instance and then use the method get. We declare an immutable variable as flights to store all the flights we can get access through the method all(), which is a method from the Model class that the Flight class inherited. Then it sends a JSON as a response. In case there is an error, it will send an error message.
appFlight.get("/flight/:id", async function(req, res) {
try {
const flight = await Flight.where('id', req.params.id).first()
if (!flight) {
res.setStatus(400).send("Flight not found")
}
req.body = flight
res.setStatus(200).json(req.body)
console.log(req.body)
} catch (err) {
console.log(err)
res.setStatus(500).send({error: 'Something went wrong'})
}
});
This router is to get a flight by its 'id'. We use the method 'where' from the Flight class. 'req.params.id' is a method in opine to retrieve the id parameter from the path. If there is no flight with the id specified in the path, it will send the message "Flight not found". If there is a flight, it will send the request body as JSON.
appFlight.use(json());
appFlight.post('/flight', function(req, res) {
const flight = Flight.create(req.body)
try {
res.setStatus(200).json(req.body)
} catch (err) {
console.log(err)
res.setStatus(500).send({error: 'Something went wrong'})
}
});
In this router we use the create method to post the request body. If the post is successful, it will send the status code '200' and the body that was posted as a response.
appFlight.put("/flight/:id", async function(req, res) {
const body = req.body
try {
const flight = await Flight.where('id', req.params.id).first()
if (!flight) {
res.setStatus(400).send("Flight not found")
}
const flights = Flight.where('id', req.params.id).update(req.body)
req.body = flights
res.setStatus(200).send({flights: "updated"})
} catch (err) {
console.log(err)
res.setStatus(500).send({error: 'Something went wrong'})
}
});
To make an update we use almost the same code we use to get a flight. The difference is that we use the update method.
The same thing is applied to deleting a row from the database. We use a delete method from the Flight class. These methods came from DenoDb. The code deletes the row its id is specified in the path.
appFlight.delete("/flight/:id", async function(req, res) {
const id = req.params.id;
try {
const flight = await Flight.where('id', req.params.id).first()
if (!flight) {
res.setStatus(400).send("Flight not found")
}
flight.delete()
res.setStatus(200).send("Flight deleted")
} catch (err) {
console.log(err)
res.setStatus(500).send({error: 'Something went wrong'})
}
});
app.ts
import { opine} from "https://deno.land/x/opine@2.1.5/mod.ts";
import { db } from "./db.ts";
import {appFlight} from "./routers/Routers.ts"
const app = opine();
const PORT = 3000;
app.get("/home", function(req, res) {
res.send("Hello, welcome to X Airlines. ")
});
app.use("/", appFlight);
await db.sync()
app.listen(PORT)
console.log(`https:\\localhost: ${PORT}`);
This file is where the server is executed. We create an opine instance, to use the 'listen' method and pass the port the API is going to use. 'db.sync()' is used to synchronize the database.
To run the server we need to write this in our terminal because Deno is secure by default:
deno run --allow-net --allow-write --allow-read app.ts
If you have any recommendations about other packages, how to improve my code, my English, anything; please leave a comment or contact me through Twitter or LinkedIn.
The source code is here
References: