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
&Repository
layers - 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.example
to.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
API
at http://localhost:8000 - Browse (for Docker only) DB
Adminer
at http://localhost:8080 - Browse
Swagger Open API
Doc 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/[email protected]/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/[email protected]/cli.ts make create_product
Package | Purpose |
---|---|
[email protected] | Deno middleware framework |
[email protected] | Read env variables |
[email protected] | MySQL driver for Deno |
[email protected] | DB migration tool for Deno |
[email protected] | validation library |
[email protected] | JWT token encoding |
[email protected] | 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
routes
folder. For each entity there should be separate routes file. For example user related CRUD router handlers are defined inuser.routes.ts
file. - All routes are bind with router handlers in
routes.ts
file. - 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
getCats
route handler with router inroutes.ts
file -
//routes.ts import * as catRoutes from "./cat.routes.ts"; // ... router initialization codes router .get("/cats", ...catRoutes.getCats);
- Create file
- Here we used [email protected] 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/[email protected]/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
Authorization
key. - Middleware JWTAuthMiddleware used to parse the
Authorization
header and decode the payload asctx.user
.
- Auth guards are dependent on the
ctx.user
provided by JWTAuthMiddleware middleware. - To define different levels of authentication guard in different route handlers, middleware userGuard defined.
userGuard
middleware 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