Hello everyone. This time I decided to do something more interesting than another bot, so next I will show you how to implement the REST API with Deno, connect and use MongoDB as a database, and run it all from under Linux.
A video version of this post is available below:
Description of the task
As an example, I chose the Github Gists API and the following methods:
[POST] Create a gist;
[GET] List public gists;
[GET] Get a gist;
[PATCH] Update a gist;
[DELETE] Delete a gist.
Project creation
First, we add a file api/mod.ts
:
console.log('hello world');
And we check that everything works with the command deno run mod.ts
:
Add dependencies
Create a file api/deps.ts
and add the following dependencies:
/* REST API */
export { Application, Router } from "<https://deno.land/x/oak/mod.ts>";
export type { RouterContext } from "<https://deno.land/x/oak/mod.ts>";
export { getQuery } from "<https://deno.land/x/oak/helpers.ts>";
/* MongoDB driver */
export { MongoClient, Bson } from "<https://deno.land/x/mongo@v0.21.0/mod.ts>";
Aside : Unlike NodeJS, the authors of Deno have dropped support for npm and node_modules
, and the necessary libraries are url-linked and cached locally. The libraries themselves can be found in the Third Party Modules section at http://deno.land .
Adding the Boilerplate API
Next, add the code to run the API to the file mod.ts
:
import { Application, Router } from "./deps.ts";
const router = new Router();
router
.get("/", (context) => {
context.response.body = "Hello world!";
});
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
Application
Router
deps.ts
.
, :
deno run --allow-net mod.ts
;
http://localhost:8000
;
'Hello world!';
: Deno secure by default. , () (--allow-net
, (--allow-read
--allow-write
, (--allow-env
) .
POST /gists
, .
:
[POST] /gists
:
content: string | body;
:
201 Created;
400 Bad Request;
handlers
create.ts
, handler () :
import { RouterContext } from "../deps.ts";
import { createGist } from "../service.ts";
export async function create(context: RouterContext) {
if (!context.request.hasBody) {
context.throw(400, "Bad Request: body is missing");
}
const body = context.request.body();
const { content } = await body.value;
if (!content) {
context.throw(400, "Bad Request: content is missing");
}
const gist = await createGist(content);
context.response.body = gist;
context.response.status = 201;
}
:
(
request.hasBody
!content
);
createGist
( );
201 Created.
, ( service.ts
):
import { insertGist } from "./db.ts";
export async function createGist(content: string): Promise<IGist> {
const values = {
content,
created_at: new Date(),
};
const _id = await insertGist(values);
return {
_id,
...values,
};
}
interface IGist {
_id: string;
content: string;
created_at: Date;
}
content: string
, IGist
.
MongoDB. db.ts
:
import { Collection } from "<https://deno.land/x/mongo@v0.21.0/src/collection/collection.ts>";
import { Bson, MongoClient } from "./deps.ts";
async function connect(): Promise<Collection<IGistSchema>> {
const client = new MongoClient();
await client.connect("mongodb://localhost:27017");
return client.database("gist_api").collection<IGistSchema>("gists");
}
export async function insertGist(gist: any): Promise<string> {
const collection = await connect();
return (await collection.insertOne(gist)).toString();
}
interface IGistSchema {
_id: { $oid: string };
content: string;
created_at: Date;
}
:
MongoDB;
gist_api
connect
;
,
gist_api
IGistSchema
;
insertOne
(inserted id);
MongoDB
, :
sudo systemctl start mongod sudo systemctl status mongod
, :
deno run --allow-net mod.ts
;
Postman API:
, 201 Created
_id
:
: , TypeScript . - Deno TypeScript .
GET /gists
, .
:
[GET] /gists
:
skip: string | query;
limit: string | query;
:
200 OK;
handlers/list.ts
, handler () :
import { getQuery, RouterContext } from "../deps.ts";
import { getGists } from "../service.ts";
export async function list(context: RouterContext) {
const { skip, limit } = getQuery(context);
const gists = await getGists(+skip || 0, +limit || 0);
context.response.body = gists;
context.response.status = 200;
}
:
query string
getQuery
;
getGists
( );
200 OK;
: number
, string
. +skip || 0
( , NaN
0
).
, :
export function getGists(skip: number, limit: number): Promise<IGist[]> {
return fetchGists(skip, limit);
}
skip: number
limit: number
, , IGist
.
MongoDB. fetchGists
db.ts
:
export async function fetchGists(skip: number, limit: number): Promise<any> {
const collection = await connect();
return await collection.find().skip(skip).limit(limit).toArray();
}
:
gist_api
connect
;
,
skip
-limit
;
deno run --allow-net mod.ts
;
Postman API:
, 200 OK
:
GET /gists/:id
.
:
[GET] /gists/:id
:
id: string | path
:
200 OK;
400 Bad Request;
404 Not Found.
handlers/get.ts
, handler () :
import { RouterContext } from "../deps.ts"
import { getGist } from "../service.ts";
export async function get(context: RouterContext) {
const { id } = context.params;
if(!id) {
context.throw(400, "Bad Request: id is missing");
}
const gist = await getGist(id);
if(!gist) {
context.throw(404, "Not Found: the gist is missing");
}
context.response.body = gist;
context.response.status = 200;
}
:
id
400 ;
getGist
404 ( );
200 OK;
, :
export function getGist(id: string): Promise<IGist> {
return fetchGist(id);
}
interface IGist {
_id: string;
content: string;
created_at: Date;
}
id: string
, IGist
.
MongoDB. fetchGist
db.ts
:
export async function fetchGist(id: string): Promise<any> {
const collection = await connect();
return await collection.findOne({ _id: new Bson.ObjectId(id) });
}
:
gist_api
connect
;
findOne
_id
;
deno run --allow-net mod.ts
;
Postman API:
, 200 OK
:
PATCH /gists/:id
.
, :
[PATCH] /gists/:id
:
id: string | path
content: string | body
:
200 OK;
400 Bad Request;
404 Not Found.
handlers/update.ts
, handler () :
import { RouterContext } from "../deps.ts";
import { getGist, patchGist } from "../service.ts";
export async function update(context: RouterContext) {
const { id } = context.params;
if (!id) {
context.throw(400, "Bad Request: id is missing");
}
const body = context.request.body();
const { content } = await body.value;
if (!content) {
context.throw(400, "Bad Request: content is missing");
}
const gist = await getGist(id);
if (!gist) {
context.throw(404, "Not Found: the gist is missing");
}
await patchGist(id, content);
context.response.status = 200;
}
:
id
400 ;
!content
;
getGist
404 ;
patchGist
( );
200 OK.
, :
export async function patchGist(id: string, content: string): Promise<any> {
return updateGist({ id, content });
}
interface IGist {
_id: string;
content: string;
created_at: Date;
}
id: string
content: string
, any
.
MongoDB. updateGist
db.ts
:
export async function updateGist(gist: any): Promise<any> {
const collection = await connect();
const filter = { _id: new Bson.ObjectId(gist.id) };
const update = { $set: { content: gist.content } };
return await collection.updateOne(filter, update);
}
:
gist_api
connect
;
filter
, ;
update
, ;
updateOne
;
deno run --allow-net mod.ts
;
Postman API:
, 200 OK
:
DELETE /gists/:id
, , .
, :
[DELETE] /gists/:id
:
id: string | path
:
204 No Content;
404 Not Found.
handlers/remove.ts
, handler () :
import { RouterContext } from "../deps.ts";
import { getGist, removeGist } from "../service.ts";
export async function remove(context: RouterContext) {
const { id } = context.params;
if (!id) {
context.throw(400, "Bad Request: id is missing");
}
const gist = await getGist(id);
if (!gist) {
context.throw(404, "Not Found: the gist is missing");
}
await removeGist(id);
context.response.status = 204;
}
:
id
400 ;
getGist
404 ;
removeGist
( );
204 No Content.
, :
export function removeGist(id: string): Promise<number> {
return deleteGist(id);
}
id: string
number
.
MongoDB. deleteGist
db.ts
:
export async function deleteGist(id: string): Promise<any> {
const collection = await connect();
return await collection.deleteOne({ _id: new Bson.ObjectId(id) });
}
:
gist_api
connect
;
deleteOne
_id
;
deno run --allow-net mod.ts
;
Postman API:
, 204 No Content
:
: . isDeleted: boolean
.
FAQ
API 404 Not Found
router
mod.ts
:
import { Application, Router } from "./deps.ts";
import { list } from "./handlers/list.ts";
import { create } from "./handlers/create.ts";
import { remove } from "./handlers/remove.ts";
import { get } from "./handlers/get.ts";
import { update } from "./handlers/update.ts";
const app = new Application();
const router = new Router();
router
.post("/gists", create)
.get("/gists", list)
.get("/gists/:id", get)
.delete("/gists/:id", remove)
.patch("/gists/:id", update);
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
API 500 Internal Server Error
const app = new Application();
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
console.log(err);
}
});
...
-
-
API;
-
-
.
, , , , -.