HATEOAS REST APIs With Node.js
https://www.aat-corp.com/blog/the-basics-of-cleaning-circuit-boards

HATEOAS REST APIs With Node.js

One of the significant achievements in our last project (Asteria) is to expose datasets through a REST API built over Node.js. Unfortunately, even if it is a major constraint of REST, it is difficult to get a simple and efficient way to implement Hypermedia As The Engine of Application State (HATEOAS). For that reason, we have decided to create a small and easy-to-use solution to add HATEOAS support to all server frameworks built on top of Node.js.

Since we are currently refactoring some code into a common package, this solution is deployed in this new module named JSAX-RS (JavaScript API for RESTful Web Services). JSAX-RS is a JavaScript programming language API that provides support in creating web services according to the REST architectural pattern.

In this article, we will introduce the JAX-RS HATEOAS API, that helps developer to easily add support to the third level of the Richardson's Maturity Model. To explain all concepts, we also will provide examples based on Galaad, the default implementation of the JAX-RS HATEOAS API.

JSAX-RS HATEOAS Design

Contrary to other empiric structures, the JSAX-RS HATEOAS API is designed around three main components that each defines a specific part of the application:

  • application: represents the set of features defined for a distributed software
  • state: specifies the application state for the current endpoint
  • transition: defines the way to navigate from the current state to another

All of these entities are exposed as interfaces to ensure weak coupling between the nodes of an application over different networks.

The JAX-RS architecture allows to easilly split monolithic structure of an application state into a uniform interface, that respects separation of concerns, while providing semantic knowledge. Consequently, HATEOAS information can be interpreted by automated processes.

The following sample shows a JSON structure as produced by the JAX-RS HATEOAS API as result of an HTTP operation:

{
    /* App data */
    "content": "Hello, User!",
    /* HATEOAS response */
    "application": {
        "name": "my-app",
        "version": "1.0",
        "authority": "api.sample-app.com",
        "state": {
            "name": "helloUser",
            "type": "document",
            "resource": "/greeting?name=User",
            "transitions": [
                {
                    "type": "collection",
                    "resource": "/users"
                }
            ]
        }
    }
}

JSAX-RS HATEOAS API

The JSAX-RS HATEOAS API is built on top of a bunch of interfaces that can be used to deploy HATEOAS over many different languages:

  • HateoasContext: the context in which application components are defined
  • Application: provides information about an application managed by Hypermedia
  • State: provides state information for the application
  • Transition: defines information about interactions between application states

Aspect-oriented Programming

Major advantage of AOP is that it deals with code complexity and tackles objects lifecycle. Code becomes more readable and maintainable.

With the help of TypeScript decorators, JAX-RS introduces AOP to manage the entire resource states of an application. Because the API defines only few decorators, with small configuration, JAX-RS HATEOAS is very easy-to-use.

HATEOAS decorators are implemented via the Galaad framework, as follow:

  • @RsApplication: provides configuration for Application objects
  • @RsHateoasContext: provides access to the HateoasContext context for the current app
  • @RsState: provides configuration for State objects
  • @RsTransition: provides configuration for Transition objects
  • @RsTransitionFromState: allows to declare a Transition object based on an existing State object
  • @RsMapTransition: allows to share a single Transition object definition between mutliple states

HATEOAS API Integration

Initializing App

By using the @RsApplication decorator, you indicate that the application navigation is driven by Hypermedia. Because only one @RsApplication decorator can be used by application, you typically apply it to the class responsible for declaring the server framework you work with:

@RsApplication({ name: 'my-app' })
export class SampleApplication {


    public run(): void {
        const app: Express = express();
        const router: Router = Router();
        app.use('/api', router);
        app.listen(3000);
    }
}

Now, the application is ready for HATEOAS.

Accessing HATEOAS Context

The HateoasContext interface allows to access all features of the JSAX-RS HATEOAS API. Developer can use the @RsHateoasContext decorator to get the reference to the HateoasContext implementation, provided as a singleton in the Galaad framework:

@RsApplication({ name: 'my-app' })
export class SampleApplication {


    @RsHateoasContext()
    private _context: HateoasContext;


    public run(): void {
        const app: Express = express();
        const router: Router = Router();
        app.use('/api', router);
        app.listen(3000);
        console.log(`application "${context.getName()}" listening on port 3000`);
    }
}

Working With App States

The State interface allows developer to set information for “a resource current state”. Resource state description is declared by using the @RsState decorator.

@RsState decorators are method decorator, which means that route declaration must be wrapped within a class method:

@RsState({
    resource: '/books',
    type: StateType.COLLECTION,
    method: HttpMethod.GET
})
private getBooks(): void {
    this._router.get('/books', (req: Request, res: Response) => {
        const result: any = {
            data: this._bookService.getAll()
        };
        res.send(result);
    });
}

And this is where the magic of AOP comes in. The Galaad API will use the name of the decorated method to expose this state to developer.

You just use the getResourceStateRepresentation() method of the HateoasContext interface to get representation of app states and send them as a response to HTTP calls:

@RsState({
    resource: '/books',
    type: StateType.COLLECTION,
    method: HttpMethod.GET
})
private getBooks(): void {
    this._router.get('/books', (req: Request, res: Response) => {
        const result: any = {
            data: this._bookService.getAll(),
            application: this._context.getResourceStateRepresentation('getBooks')
        };
        res.send(result);
    });
}
}

Adding App Transitions

The Transition interface allows to set information for “a resource desired state”. Resource state description is declared by using the @RsTransition decorator. @RsTransition is both, method and property decorator.

In the following code sample, adds a transition description to the previous getBook resource state:

@RsState({
	resource: '/books/:bookId',
	type: StateType.COLLECTION,
	method: HttpMethod.GET
})
@RsTransition({
	resource: '/books',
	type: StateType.COLLECTION,
	method: HttpMethod.GET
})
private getBook(): void {
	this._router.get('/books/:bookId', (req: Request, res: Response) => {
		const bookId: string = req.params.bookId;
		this._bookService.read(bookId, (err: any, book: Book)=> {
			if (err) {
				res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR);
			} else {
				const stateParams: any = { bookId: bookId };
				const appState: any = this._context.getResourceStateRepresentation('getBook', stateParams);
				const result: any = {
					data: book,
					application: appState
				};
				const status: HttpStatusCode = book ? HttpStatusCode.OK : HttpStatusCode.NOT_FOUND;
				res.status(status).send(result);
			}
		});
	});
}

This example will produce the following result as body of the HTTP response:

{
    "data": {
        "id": "the-trial",
        "name": "The Trial",
        "author": "Franz Kafka",
        "year": "1925"
    },
    "application": {
        "name": "my-app",
        "state": {
            "type": "collection",
            "resource": "/books/the-trial",
            "method": "GET",
            "transitions": [
                {
                    "type": "collection",
                    "resource": "/books",
                    "method": "GET"
                }
            ]
        }
    }
}

As you can see, Galaad is able to replace dynamically parameters according to data associated with the current resource.

When used as a property decorator, @RsTransition allows to easily share transitions between different states. Galaad uses the name of the decorated property to resolve the reference to the associated transition.

Developer uses the @RsMapTransition decorator to make relation between states and shared transitions.

@RsTransition({
	resource: '/books',
	type: StateType.COLLECTION,
	method: HttpMethod.GET
})
private getBooksTransition: TransitionConfig;


@RsState({
	resource: '/books/:bookId',
	type: StateType.COLLECTION,
	method: HttpMethod.GET
})
@RsMapTransition('getBooksTransition')
private getBook(): void {
	// rest of the code here
}


@RsState({
	resource: '/books',
	type: StateType.COLLECTION,
	method: HttpMethod.POST
})
@RsMapTransition('getBooksTransition')
private createBook(): void {
	// rest of the code here
}

As we raised before, a transition is just set of metadata that indicates relations between the current application state and a new desired state. Thus, JSAX-RS provides another useful feature, which is the ability to create transitions from existing state declarations.

By passing the name of a state to the @RsTransitionFromState decorator, developer automatically creates a new transition declaration from this state:

@RsTransitionFromState('getBooks')
public getBooksTransition: TransitionMapping;


@RsTransitionFromState('getBook')
public getBookTransition: TransitionMapping;


@RsState({
	resource: '/books',
	type: StateType.COLLECTION,
	method: HttpMethod.GET
})
@RsMapTransition('getBookTransition')
private getBooks(): void {
	// rest of the code here
}


@RsState({
	resource: '/books/:bookId',
	type: StateType.COLLECTION,
	method: HttpMethod.GET
})
@RsMapTransition('getBooksTransition')
private getBook(): void {
	// rest of the code here
}


@RsState({
	resource: '/books',
	type: StateType.COLLECTION,
	method: HttpMethod.POST
})
@RsMapTransition('getBooksTransition')
@RsMapTransition('getBookTransition')
private createBook(): void {
	// rest of the code here
}

JSAX-RS HATEOAS API is really flexible and ships with a minimal set of easy-to-use components.

Where To Go From Here...

This article is just a basic introduction to the JSAX-RS HATEOAS API and the Galaad framework. Full documentation is available from the project repository.

For a complete working example of the JAX-RS HATEOAS API, you can download the Galaad Sample Application.

To view or add a comment, sign in

More articles by Pascal ECHEMANN

  • JEC: Working With jslets in Node.js

    This article shows how to create web applications, a la JAVA, over Node.js by using the "JavaScript Enterprise…

  • Understanding SoC In The WOOZ Framework

    This article is about SoC architecture in the WOOZ HTML5 framework. For more information, please visit the WOOZ website…

Others also viewed

Explore content categories