Skip to main content
Deno 2 is finally here 🎉️
Learn more

oak_nest

deno version Denon License

Rely on oak@v9.0.0 to simulate some annotation functions of nestjs which is a frame for nodejs.

I will update the oak version if need.

run

deno run --allow-net --allow-env --allow-write example/main.ts

or you can use denon

denon dev

Demo

Controller

Decorators ControllerUseGuardsGetPostBodyHeadersQueryResReq now are available:

import {
  Body,
  Context,
  Controller,
  createParamDecorator,
  createParamDecoratorWithLowLevel,
  ForbiddenException,
  Get,
  Headers,
  Post,
  Query,
  Res,
  UseGuards,
} from "https://deno.land/x/oak_nest@v0.1.2/mod.ts";
import type { CanActivate } from "https://deno.land/x/oak_nest@v0.1.2/mod.ts";
import mockjs from "https://deno.land/x/deno_mock@v2.0.0/mod.ts";
import { delay } from "https://deno.land/std/async/mod.ts";

class AuthGuard implements CanActivate {
  async canActivate(context: Context): Promise<boolean> {
    console.log("--AuthGuard---");
    await delay(100);
    // throw new ForbiddenException('this is AuthGuard error');
    return true;
  }
}

class AuthGuard2 implements CanActivate {
  async canActivate(context: Context): Promise<boolean> {
    console.log("--AuthGuard2---");
    return true;
  }
}

class AuthGuard3 implements CanActivate {
  async canActivate(context: Context): Promise<boolean> {
    throw new ForbiddenException("this is AuthGuard3 error");
    return false;
  }
}

@UseGuards(AuthGuard)
@Controller("/user")
export class UserController {
  @UseGuards(AuthGuard2, AuthGuard3)
  @Get("/info/:id")
  test(
    context: Context,
    @add() name: string,
    @Query() params: any,
    @Query("age") age: string,
  ) {
    console.log(params, age);
    context.response.body = "role info " + name + " - " +
      JSON.stringify(params);
  }

  @Get("/info")
  getInfo(@Res() res: Response, @Query() params: any) {
    console.log(params);
    res.body = "role get info " + JSON.stringify(params);
  }

  @Get("list")
  list(context: Context) {
    console.log("---list----");
    this.testInnerCall();
    context.response.body = "list";
  }

  testInnerCall() {
    console.log("---test---");
  }
}

You can customize the decorator by createParamDecorator or createParamDecoratorWithLowLevel:

const Add = createParamDecorator(async (ctx: any) => {
  const result = ctx.request.body(); // content type automatically detected
  if (result.type === "json") {
    const value = await result.value; // an object of parsed JSON
    // console.log('value', value);
    return value.userId;
  }
});

function Add2(params: any) {
  return createParamDecoratorWithLowLevel(async (ctx: any) => {
    return params;
  });
}

then use like this:

@Post("/info")
info(
  @Add() name: string,
  @Add2("name") name2: string,
  @Body() params: any,
  @Headers() headers: any,
  @Headers("host") host: any,
  @Res() res: Response,
) {
  console.log("ctx", name, name2, params, headers, host);
  res.body = "role info " + name + name2;
}

or you can use class validator like this:

class Dto {
  @Max(2)
  @Min(1)
  pageNum!: number;

  @Max(5)
  @Min(1)
  pageCount!: number;
}

@Post("/info")
info(
  @Add() name: string,
  @Body(Dto) params: Dto,
  @Headers() headers: any,
  @Headers("host") host: any,
  @Res() res: Response,
) {
  console.log("ctx", name, params, headers, host);
  return "role info " + name;
}

it is using deno_class_validator for validator, which is forked from class-validator which is using in nodejs, if it fails, then will throw an Error.

I cannot get the type of dto directly like nestjs did, so now you have to pass one more parameter in the body as @Body(Dto) params: Dto. If you have a good idea, please give me a suggestion, then thanks much.

router add Controller

import { UserController } from "./user.controller.ts";
import { User2Controller } from "./user2.controller.ts";
import { RoleController } from "./role.controller.ts";
import { Router } from "https://deno.land/x/oak_nest@v0.1.2/mod.ts";

const router = new Router();
router.add(UserController);
router.setGlobalPrefix("api");
router.add(RoleController, User2Controller);

use router in app

import {
  Application,
  isHttpError,
  send,
  Status,
} from "https://deno.land/x/oak_nest@v0.1.2/mod.ts";
import router from "./router/index.ts";

const app = new Application();

// Timing
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`);
});

app.use(router.routes());

const port = Number(Deno.env.get("PORT") || 1000);
console.log(`app will start with: http://localhost:${port}`);
await app.listen({ port });

now you can visit http://localhost:1000/api/user/info,http://localhost:1000/api/user/list.

You can see more in the example dirs.