This is a starter project to create Deno RESTful API using oak. oak is a middleware framework and router middleware for Deno, inspired by popular Node.js framework Koa and @koa/router.
This project covers
- Swagger Open API doc
- Docker container environment
- JWT authentication
- User authorization
- Request validation
- .env config management
- Coding architecture with
Router,Service&Repositorylayers - Application Error Handling
- Request timing logging
- Generic request logging
- Setup
- Migrations
- Modules
- Project Layout
- How to add a new route
- How to validate request body
- How to use JWT authorization
- How to add auth guards
- Error handling
- Contributing
- Contributors
- Roadmap
We can run the project with/ without Docker.
-
Pre-Requisite
- For dockerized environment we need
- docker,
- docker-compose installed.
- To run API server without Docker we need
- MySQL server running &
- Deno run time installed
- For dockerized environment we need
-
Configuration
- In application root, rename example env file
env.exampleto.env. - An example env file contains MySQL credentials for the dockerized environment. For non-docker setup, update MySQL credentials here.
- In application root, rename example env file
-
Run API
- For Docker: Up docker-compose, this will create a docker container with the database with the given name in env.
$ docker-compose up --build- For non-docker run API server with Deno run time
$ deno run --allow-read --allow-net app.ts -
API
- Browse
APIat http://localhost:8000 - Browse (for Docker only) DB
Adminerat http://localhost:8080 - Browse
Swagger Open APIDoc at http://localhost:8105
- Browse
We use nessie to manage database migration.
- In the application root, we have
nessie.config.ts. Make sure to update this with the DB credentials. - Run the following command to run the migration. Migration should create necessary tables and dump the data.
$ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie@v1.0.0-rc3/cli.ts migrate
With this, the user table would be created and the table would be seeded with fake data
- Further, to add new migration, for example, to create new product table run
deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie@v1.0.0-rc3/cli.ts make create_product
| Package | Purpose |
|---|---|
| oak@v5.0.0 | Deno middleware framework |
| dotenv@v0.4.2 | Read env variables |
| mysql@2.2.0 | MySQL driver for Deno |
| nessie@v1.0.0-rc3 | DB migration tool for Deno |
| validasaur@v0.7.0 | validation library |
| djwt@v0.9.0 | JWT token encoding |
| bcrypt@v0.2.1 | bcrypt encription lib |
.
├── .env (Make sure to create this file from given .env.example)
├── config/
| |── config.ts (configuration object)
├── db/
| |── migrations/
| |── seeds/
| ├── db.ts (DB connection object)
├── middlewares/
├── migrations/
├── services/
├── repositories/
├── helpers/
├── routes/
|── types/
|── types.ts (all types exported here)
├── app.ts (application server)
├── openapi.yml (Swagger open api definition)
└── nessie.config.ts (DB configuration for nessie migration)
- Router hanlders are defined in
routesfolder. For each entity there should be separate routes file. For example user related CRUD router handlers are defined inuser.routes.tsfile. - All routes are bind with router handlers in
routes.tsfile. - To create CRUD for
cat- Create file
cat.routes.ts - Write router handler methods,
//cat.routes.ts import * as catService from "./../services/cat.service.ts"; /** * get list of cats */ const getCats = [ async (ctx: Context) => { const cats = await catService.getCats(); ctx.response.body = cats; } ]; //export route handler methods exports { getCats };- Then bind
getCatsroute handler with router inroutes.tsfile -
//routes.ts import * as catRoutes from "./cat.routes.ts"; // ... router initialization codes router .get("/cats", ...catRoutes.getCats); - Create file
- Here we used validasaur@v0.7.0 module for validating forms or request body. List of available rules can be found here
- requestValidator middleware added to validate the request body.
//auth.routes.ts
import {
required,
isEmail,
} from "https://deno.land/x/validasaur@v0.7.0/src/rules.ts";
import { requestValidator } from "./../middlewares/request-validator.middleware.ts";
/**
* request body schema
* for cat create/update
* */
const catSchema = {
name: [required],
email: [required, isEmail],
};
/**
* create cat
*/
const createCat = [
/** request validation middleware */
requestValidator({ bodyRules: catSchema }),
/** router handler */
async (ctx: Context) => {
// ... router handler code to create cat
},
];
- Here, We used JWT based authentication
- Necessary JWT constants should be configured in
.env(copy from.env.example).
# Access token validity in ms
JWT_ACCESS_TOKEN_EXP=600000
# Refresh token validity in ms
JWT_REFRESH_TOKEN_EXP=3600000
# Secret secuirity string
JWT_TOKEN_SECRET=HEGbulKGDblAFYskBLml
- Request header should contain JWT bearer token as
Authorizationkey. - Middleware JWTAuthMiddleware used to parse the
Authorizationheader and decode the payload asctx.user.
- Auth guards are dependent on the
ctx.userprovided by JWTAuthMiddleware middleware. - To define different levels of authentication guard in different route handlers, middleware userGuard defined.
userGuardmiddleware optionally takes allowed user's roles as parameter. Otherwise, it will check only for the signed user.- Here is the example usage:-
//user.routes.ts
/**
* get list of users
* user with ADMIN role only can access
*/
const getUsers = [
userGuard(UserRole.ADMIN),
async (ctx: Context) => {
// ... route handlers code
},
];
/**
* get signed user detail
* any authenticated user can access
*/
const getMe = [
userGuard(),
async (ctx: Context) => {
// ... route handlers code
},
];
Bug reports and pull requests are welcome on GitHub at https://github.com/asad-mlbd/deno-api-starter-oak.
- Open API integration
- API Doc
- Validation
- JWT Auth
- Unit Testing
- Logger
