This is a simple Boilerplate project to create Deno RESTful APIs using Oak and deno_mongo
- Model, Controller, Service based project structure
- MongoDB
- JWT authentication
- User authorization
- CORS
- environment management using .env
- Request validation
- Error Handling
- Database seeding
- User Roles and Rights
- User History
- Password Hashing using BCrypt
- Denon Integration
- Integration tests
- Docker Integration
- CircleCI Integration
- Oak - A middleware framework for Deno's net server
- deno_mongo - MongoDB driver for Deno
- cors - Deno.js CORS middleware
- djwt - To make JSON Web Tokens in deno. Based on JWT and JWS specifications.
- yup - Schema builder for value parsing and validation
- bcrypt - OpenBSD Blowfish password hashing algorithm
Using Deno:
deno upgrade --version 1.20.6
With Shell:
curl -fsSL https://deno.land/x/install/install.sh | sh -s v1.20.6
With PowerShell:
$v="1.20.6"; iwr https://deno.land/x/install/install.ps1 -useb | iex
Clone this repository to your local machine
git clone https://github.com/vicky-gonsalves/deno_rest.git
Directory Structure
| .gitignore
| app.ts
| denon.json
| deps.ts
| docker-compose.dev.yml
| docker-compose.test.yml
| docker-compose.yml
| Dockerfile
| LICENSE
| lock.json
| lock_update.sh
| README.md
| reload_deps.sh
| run_tests.sh
| seed.ts
| tsconfig.json
+---.circleci
| config.yml
|
+---config
| config.ts
| roles.ts
|
+---controllers
| auth.controller.ts
| user.controller.ts
|
+---data
| users.json
|
+---db
| db.ts
|
+---environments
| .env.development
| .env.example
| .env.test
|
+---helpers
| hash.helper.ts
| jwt.helper.ts
|
+---middlewares
| auth.middleware.ts
| errorHandler.middleware.ts
| logger.middleware.ts
| validate.middleware.ts
|
+---models
| token.model.ts
| user.model.ts
| user_history.model.ts
|
+---routers
| auth.router.ts
| default.router.ts
| index.ts
| user.router.ts
|
+---services
| auth.service.ts
| token.service.ts
| user.service.ts
|
+---tests
| +---fixtures
| | users.fixtures.ts
| |
| +---integration
| | | app.test.ts
| | |
| | \---users
| | user-get.test.ts
| | user-post.test.ts
| |
| \---utils
| utils.ts
|
+---types
| types.interface.ts
|
\---validations
| auth.validation.ts
| user.validation.ts
Review .environments/.env.example
file and create required .env
file
suitable to your needs. For example: for development environment create a file
.env.development
under .environments
directory for test environment create a
file .env.test
under .environments
directory and add necessary variables.
If its your first run, please install denon
from
https://deno.land/x/denon If there is error while
installing denon, refer this solution:
denosaurs/denon#122 (comment)
To install dependencies, run following command in your terminal. Note: Terminal path must be project directory's root path
deno cache --reload --unstable --lock-write --lock=lock.json ./deps.ts
OR run the reload_deps.sh
file from the project directory
This will automatically download all the dependencies and update lock.json
file
If you need to seed (run migrations) database initially, Simply enable
SEED=true
in your .env file You can add or edit any seed file under data
directory. Basic example of seed file is provided in data/users.json
Logic to control seed is located in seed.ts
where you can add multiple seed
files as follow:
const seedCollections: Array<Record<string, boolean>> = [
{ users: true }, // collection_name: boolean
{ posts: true },
{ comments: false},
...
...
Note: file name must be in collection_name.json
pattern
In your project root open terminal and run following command to run the project
denon start
User roles are saved in config/roles.ts
file as:
export const roles = ["user", "admin"];
and Rights are saved as:
roleRights.set(roles[0], [
"getMe",
]);
roleRights.set(roles[1], [
"getMe",
"getUsers",
"manageUsers",
]);
You can add/edit roles and rights as per your requirements.
All routes are stored under routers
directory Below is the example of
/api/users
route. This route is JWT protected In user.router.ts:
...
/** JWT protected route */
router.post(
"/api/users", // route
auth("manageUsers"), // Auth Guard based on djwt
validate(createUserValidation), // Yup based validation
UserController.create, // Controller Function
);
...
...
Non-JWT protected route:
router.post(
"/api/auth/login",
validate(loginValidation), // Yup based validation
AuthController.login,
);
All models are under models
directory example of User Model:
import db from "../db/db.ts";
export interface UserSchema {
_id: string;
name: string;
email: string;
password: string;
role: string;
docVersion: number;
isDisabled: boolean;
createdAt?: Date;
updatedAt?: Date;
}
export const User = db.getDatabase.collection<UserSchema>("users");
Controllers are saved under controllers
directory Example of User Controller:
...
class UserController {
/**
* Create User function
* @param request
* @param response
* @returns Promise<void>
*/
public static async create(
{ request, response }: RouterContext<string>,
): Promise<void> {
const body = request.body();
const {
name,
email,
password,
role,
isDisabled,
} = await body.value;
log.debug("Creating user");
response.body = await UserService.createUser({
name,
email,
password,
role: role || roles[0],
isDisabled: typeof isDisabled === "boolean" ? isDisabled : false,
});
}
...
...
All Services are under services
directory Example of User service:
class UserService {
/**
* Create user Service
* @param options
* @returns Promise<string | Bson.ObjectId | Error> Returns Mongo Document of user or error
*/
public static async createUser(
options: CreateUserStructure,
): Promise<string | Bson.ObjectId | Error> {
const { name, email, password, role, isDisabled } = options;
const hashedPassword = await HashHelper.encrypt(password);
const createdAt = new Date();
const user: string | Bson.ObjectId = await User.insertOne(
{
name,
email,
password: hashedPassword,
role,
isDisabled,
createdAt,
docVersion: 1,
},
);
if (user) {
await UserHistory.insertOne(
{
user: user as string,
name,
email,
password: hashedPassword,
role,
isDisabled,
createdAt,
docVersion: 1,
},
);
} else {
log.error("Could not create user");
return throwError({
status: Status.BadRequest,
name: "BadRequest",
path: "user",
param: "user",
message: `Could not create user`,
type: "BadRequest",
});
}
return user;
}
...
...
In your project terminal run following command to update lock.json
file with
latest dependencies
deno cache --lock=lock.json --lock-write --unstable ./deps.ts
OR simply run lock_update.sh
file
- Denon does not automatically restarts
All PRs are welcome
MIT