1.1.18 • Published 8 months ago

adonis5-nats-broker v1.1.18

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

adonis5-nats-broker

AdonisJS is a Node.js framework, and hence it requires Node.js to be installed on your computer. To be precise, we need at least the latest release of Node.js v14.

Adonis, microservices, nats , messageBroker

REST to NATS Proxy

The REST to NATS proxy project sohlich/nats-proxy is the micro framework that provides a bridge between HTTP and NATS. To introduce the problem, we first compare the HTTP and NATS communication models. The table below represents the matching of HTTP and NATS concepts and what do they provide.

Table of contents

Installation

You can install the adonis5-nats-broker package using npm:

npm i adonis5-nats-broker  

Additionally, you can set up a NATS server using Docker:

version: '3.7'  
services:  
nats:  
image: 'nats'  
ports:  
- '4222:4222'  

Start the NATS server with:

docker-compose up nats  

Configuration

Initialize and configure the adonis5-nats-broker package:

node ace configure adonis5-nats-broker  
node ace init:nats:handler  

Exception handling

AdonisJS uses exceptions for flow control instead of excessive conditionals. Exception handling can be done globally and with custom exceptions.

node ace init:nats:handler  

# CREATE: app/Exceptions/Nats/Handler.ts  

Handling exceptions globally

Exceptions during NATS requests are handled by the global exception handler, typically located in app/Exceptions/Nats/Handler.ts. You can customize this handler to handle specific exceptions, such as validation failures.

import Logger from '@ioc:Adonis/Core/Logger'  
import NatsExceptionHandler from '@ioc:Adonis/Addons/NatsExceptionHandler'  
  
export default class ExceptionHandler extends NatsExceptionHandler {  
	constructor() {  
		super(Logger)  
	}  
  
	public async handle(error: any, ctx: NatsContextContract) {  
		/**  
		* Self handle the validation exception  
		*/  
		if (error.code === 'E_VALIDATION_FAILURE') {  
			return ctx.response.status(422).send(error.messages)  
		}  
		  
		/**  
		* Forward rest of the exceptions to the parent class  
		*/  
		return super.handle(error, ctx)  
	}  
		  
	public async report(error: any, ctx: NatsContextContract) {  
		if (!this.shouldReport(error)) {  
			return  
		}  
		  
		if (typeof error.report === 'function') {  
			error.report(error, ctx)  
			return  
		}  
		  
		someReportingService.report(error.message)  
	}  
}  

Custom exceptions

You can create custom exceptions using the node ace make:nats:exception command. These exceptions can be raised in your code to handle specific error cases. You can customize the handling and reporting of these exceptions as needed.

node ace make:nats:exception UnAuthorized  

# CREATE: app/Exceptions/Nats/UnAuthorizedException.ts  

Next, import and raise the exception as follows.

import UnAuthorized from 'App/Exceptions/Nats/UnAuthorizedException'  
  
const message = 'You are not authorized'  
const status = 403  
const errorCode = 'E_UNAUTHORIZED'  
  
throw new UnAuthorized(message, status, errorCode)  
  

You can self-handle this exception by implementing the handle method on the exception class.

import { Exception } from '@adonisjs/core/build/standalone'  
import { NatsContextContract } from '@ioc:Adonis/Addons/NatsContext'  
  
export default class UnAuthorizedException extends Exception {  
	public async handle(error: this, ctx: NatsContextContract) {  
		ctx.response.status(error.status).send(error.message)  
	}  
}  
// app/Exceptions/Nats/UnAuthorizedException.ts  
  

Optionally, implement the report method to report the exception to a logging or error reporting service.

import { Exception } from '@adonisjs/core/build/standalone'  
import { NatsContextContract } from '@ioc:Adonis/Addons/NatsContext'  
  
export default class UnAuthorizedException extends Exception {  
	public report(error: this, ctx: NatsContextContract) {  
		reportingService.report(error.message)  
	}  
}  
// app/Exceptions/Nats/UnAuthorizedException.ts  
  

Controllers

Controllers are essential for handling NATS requests in AdonisJS. They help organize route handling logic.

import { NatsContextContract } from '@ioc:Adonis/Addons/NatsContext'  
  
export default class PostsController {  
	public async index(ctx: NatsContextContract) {  
	return [  
			{ id: 1, title: 'Hello world'},  
			{ id: 2, title: 'Hello universe'},  
		]  
	}  
}  

You will have to reference it as a route handler inside the start/routes.ts file to use this controller.

Broker.route('get.posts', 'PostsController.index')  

Applying middleware

Middleware can be applied to routes using the .middleware method. Middleware functions execute before the route handler. You can enable and disable middleware for specific routes.

Broker.middleware('userAuth')  
Broker.route('get.posts', 'PostsController.index')  
Broker.middleware()  

Controllers location

By convention, controllers are stored in the app/Controllers/Nats directory, but their location can be configured in the config/nats.ts file.

{  
	"namespaces": {  
		"controllers": "App/Controllers/Nats"  
	}  
}  

Creating Controllers

You can generate controllers using the node ace make:nats:controller command.

node ace make:nats:controller Post  

# CREATE: app/Controllers/Nats/UserController.ts  

Middleware

Middleware functions are executed before route handlers and can modify requests or responses.

Basic example

You can attach middleware to routes using Broker.middleware. Middleware functions can be simple inline functions.

// Enabling middleware for below routes  
Broker.middleware('userAuth')  
  
Broker.route('get.users', 'UsersController.index')  
Broker.route('post.users', 'UsersController.store')  
Broker.route('put.users.{id}', 'UsersController.update')  
Broker.route('get.users.{id}', 'UsersController.detail')  
Broker.route('delete.users.{id}', 'UsersController.delete')  
  
// Disabling middleware for any routes after below middleware  
Broker.middleware()  

Middleware classes

It's recommended to create middleware as classes to keep your code organized. Middleware classes are stored in the app/Middleware/Nats directory. Each class must implement the handle method.

node ace make:nats:middleware LogRequest  
  
// CREATE: app/Middleware/LogRequest.ts  
  

Registering middleware

For the middleware to take effect, it must be registered as a global middleware or a named middleware inside the start/kernel.ts file.

Global middleware

Global middleware are executed for all the NATS requests in the same sequence as they are registered.

You register them as an array inside the start/kernel.ts file, as shown below:

Server.middleware.register([  
	() => import('App/Middleware/Nats/LogRequest')  
])  

Named middleware

Named middleware allows you to selectively apply middleware on your routes. You begin by registering them with a unique name and later reference it on the route by that name.

Server.middleware.registerNamed({  
	auth: () => import('App/Middleware/Nats/Auth')  
})  
Passing config to named middleware

Named middleware can also accept runtime config through the handle method as the third argument. For example:

export default class Auth {  
	public async handle({ request, response }: NatsContextContract, next: () => Promise<void>, guards?: string[]) {  
		console.log(guards)  
	}  
}  

In the above example, the Auth middleware accepts an optional guards array. The user of the middleware can pass the guards as follows:

import Broker from "./Broker";  
  
Broker.middleware('auth:web,api')  
Broker.route('get.profile', 'UserController.profile')  
  

Testing

AdonisJS has out of the box support for testing, and there is no need to install any third-party packages for the same. Just run the node ace test and the magic will happen.

Configuring

Register Nats test plugin in to array inside the tests/bootstrap.ts file, as shown below:

import {natsClient} from 'adonis5-nats-broker/build/src/test'  
import Broker from '@ioc:Adonis/Addons/NatsBroker'  
  
export const plugins: Required<Config>['plugins'] = [  
	assert(),  
	runFailedTests(),  
	apiClient(),  
	natsClient(Broker),  
]  

The primary use case for the NATS client is to test JSON responses. However, there are no technical limitations around other response types like HTML, or even plain text.

import {test} from '@japa/runner'  
  
test.group('Users', () => {  
	test('get users', async ({ broker }) => {  
		const response = await broker.request('get.users', {}, { qs:{}, headers:{} })  
		response.assertStatus(200)  
		response.assertBodyContains([])  
	})  
})  

Example usage

Here's an example of how to use adonis5-nats-broker in your AdonisJS application:

start/broker.ts:

In this file, you can configure routes and middleware for NATS handling:

import Broker from '@ioc:Adonis/Addons/NatsBroker'

Broker.middleware("auth") // Enable middleware for specific routes
Broker.route('get.users', 'UsersController.index')
Broker.middleware() // Disable auth middleware

// Route without named middleware
Broker.route('get.countries', 'CountriesController.index')
  

app/Controllers/Nats/UserController.ts:

A sample controller handling NATS requests:

import type {NatsContextContract} from '@ioc:Adonis/Addons/NatsContext'  
import {schema} from "@ioc:Adonis/Core/Validator";  
import UpdateUserValidator from 'App/Validators/UpdateUserValidator'
  
export default class UserController {  
	public async index({response}: NatsContextContract) {  
		return response.ok([])  
	}  
	
	public async store({ request, response }: HttpContextContract) { 
		 const userSchema = schema.create({  
			first_name: schema.string({}, [rules.minLength(5), rules.maxLength(30)]),  
			last_name: schema.string({}, [rules.minLength(5), rules.maxLength(50)]),  
		})
		/** Validate request body against the schema */  
		const payload: any = await request.validate({schema:userSchema })
		/** store new user in database */  
		const user: User = await User.create(payload) 
		return response.created({  
			message: 'The user was successfully created',  
			data: user,  
		})  
	}
	
	public async update({ request, response }: HttpContextContract) { 
			/** Validate request body against the schema */  
			const payload: any = await request.validate(UpdateUserValidator)  
			/** store new user in database */  
			const user: User = await User.create(payload) 
			return response.created({  
				message: 'The user was successfully created',  
				data: user,  
			})  
	}
}  

App/Middleware/Nats/Auth.ts:

Sample middleware for authentication:

import type {NatsContextContract} from '@ioc:Adonis/Addons/NatsContext'  
  
export default class Auth {  
	public async handle({request, response}: NatsContextContract) {  
		// code for middleware goes here. ABOVE THE NEXT CALL  
		if (!request.header('authorization'))  
		return response.unauthorized({message: "authorization token is required"})  
		request.set('user', { name: 'jon', family: 'doe'})
	}  
}  
1.1.18

8 months ago

1.1.17

8 months ago

1.1.16

8 months ago

1.1.15

8 months ago

1.1.14

8 months ago

1.1.13

8 months ago

1.1.12

8 months ago

1.1.11

8 months ago

1.1.10

8 months ago

1.1.9

8 months ago

1.1.8

8 months ago

1.1.7

8 months ago

1.1.6

8 months ago

1.1.5

8 months ago

1.1.4

8 months ago

1.1.3

8 months ago

1.1.2

8 months ago

1.1.1

8 months ago

1.1.0

8 months ago

1.0.35

8 months ago

1.0.34

8 months ago

1.0.33

8 months ago

1.0.32

8 months ago

1.0.31

8 months ago

1.0.30

8 months ago

1.0.29

8 months ago

1.0.28

8 months ago

1.0.27

8 months ago

1.0.26

8 months ago

1.0.25

9 months ago

1.0.24

9 months ago

1.0.23

9 months ago

1.0.22

9 months ago

1.0.21

9 months ago

1.0.20

9 months ago

1.0.19

9 months ago

1.0.18

9 months ago

1.0.17

9 months ago

1.0.16

9 months ago

1.0.15

9 months ago

1.0.14

9 months ago

1.0.13

9 months ago

1.0.12

9 months ago

1.0.11

9 months ago

1.0.10

9 months ago

1.0.9

9 months ago

1.0.8

9 months ago

1.0.7

9 months ago

1.0.6

9 months ago

1.0.5

9 months ago

1.0.4

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago

1.0.0

10 months ago