GithubHelp home page GithubHelp logo

tkottke90 / hateos-url-manager Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 0.0 145 KB

Browser and Express JS Utility for creating and maintaining a HATEOS API

License: MIT License

Shell 1.26% TypeScript 98.74%

hateos-url-manager's Introduction

Javascript HATEOAS Route Manager

NodeJS utility for managing path segments and the creation of HATEOS links. This project is born out of the desire to make the APIs I create more discoverable.


Install

To install, simply install the github repository:

npm install git+ssh://[email protected]/tkottke90/javscript-hateos-router.git

Usage

The core unit in this module is the Route. Each route is defined by a path string (without a leading slash)

import { Route } from '@tkottke/javscript-hateos-routes';

const POSTS = new Route('posts');
const POST_WITH_ID = POSTS.nest(':postId')

The route instance primarily has 2 functions:

  1. Provide a express path string for controller configuration (using the path property)
  2. Provide a mechanism for creating fully qualified copies of those urls (using the url() class method)

Once you have your route instance created, you can then apply that to your controller:

import express from 'express';
import { POSTS, POST_WITH_ID } from './routes/posts'
import { getPostHandler, getPostByIDHandler } from './controllers/posts';

const app = express();

app.use(POSTS.path, getPostHandler);
app.use(POST_WITH_ID.path, getPostByIDHandler);

export app;

To generate a link url, you can then use the url method to output a populated copy of that url:

import { Request, Response, NextFunction } from 'express';
import { POST_WITH_ID } from '../routes/posts'

export function getPostByIDHandler(req: Request, res: Response, next: NextFunction) {
  const post = db.post.getById();

  res.json({
    id: post.id,
    author: post.author,
    createdAt: post.createdAt
    comments: [],

    links: {
      self: POST_WITH_ID.url({ postId: post.id })
    }
  })
}

The POST_WITH_ID route has 1 defined parameter (:postId) which the class is able to identify from the input string (:postId). This function would output the following JSON body to the caller:

{
  "id": 1,
  "author": "John Smith",
  "createdAt": "2024-02-08T17:01:58.295Z",
  "comments": [],
  "links": {
    "self": "/posts/1"
  }
}

Path vs Full Path

One of the primary pillars of this library was the ability to compose routes. The benefit of composition is it allows for for the avoidance of repeated path string. The challenge that comes with this approach is that the url function needs all the route parameters which may be defined in a parent route.

Related to this challenge there are 2 properties defined on a Route related to the Route's underlying path:

path: The path property matches what is passed into the new Route() or Route.nested(). This string is exposed for use with controllers and when constructing routes it is imperative that you think about your routes from the perspective of your controller as this is what you would otherwise define as the path pattern for your controller

fullPath: The fullPath property includes every portion of the path related to that Route instance. This would allow you to define routes at a global level without nesting.

The following example shows the route setup for User endpoints and a endpoint related to Posts related to that user:

import { Route } from '@tkottke/javscript-hateos-routes';

const USERS = new Route('users');
const USERS_WITH_ID = USERS.nested(':userId');
const USERS_POSTS = USERS.nested(':userId/posts');

If we inspect the USERS_WITH_POSTS properties we will see the following:

console.log('Path: ', USERS_POSTS.path);
console.log('Full Path: ', USERS_POSTS.fullPath);

// Output
// 
// Path: /:userId/posts
// Full Path: /users/:userId/posts

If you are using ExpressJS and their Router module, you would want to use the path property because you would be assigning other parts of your path at the router.use or app.use level:

import { Router } from 'express';
import { USERS_WITH_ID, USERS_POSTS } from '../routes/posts'

const userController = Router();

userController.get('/', findUserHandler);
userController.get(USERS_WITH_ID.path, getUserByIdHandler);
userController.get(USERS_POSTS.path, getUsersPostsHandler);

export userController;

In your server setup you may then use the router with the root USER route definition:

import express from 'express';
import UserController from './controllers/user.controller';
import { USERS } from './routes'

const app = express();

app.use(USERS.path, UserController);

export app;

Alternatively, if you were creating a simple API where segmentation did not make sense, you could recreate the same effect by using the fullPath property:

import express from 'express';
import UserController from './controllers/user.controller';
import { USERS, USERS_WITH_ID, USERS_POSTS } from './routes'

const app = express();

app.get(USERS.fullPath, findUserHandler);            // /users
app.get(USERS_WITH_ID.fullPath, getUserByIdHandler); // /users/:userId
app.get(USERS_POSTS.fullPath, getUsersPostsHandler); // /users/:userId/posts

export app;

Query & Hash Parameters

The url method is designed to populate a path to a resource. This typically is done using "path parameters" or items in the url path itself to target a resource. However, you may want to accommodate other urls that use search or hash parameters.

To support this you can

import { Request, Response, NextFunction } from 'express';
import { USERS } from '../routes/users'

export function getPostByIDHandler(req: Request, res: Response, next: NextFunction) {
  const user = db.user.getById();

  res.json({
    id: user.id,
    displayName: user.display,
    createdAt: user.createdAt

    links: {
      self: USERS.url(undefined, { query: { userId: user.id } })
    }
  })
}

This will produce the following output:

{
  "id": 1,
  "author": "John Smith",
  "createdAt": "2024-02-08T17:01:58.295Z",

  "links": {
    "self": "/users?userId=1"
  }
}

Same thing if you pass the hash property:

{
  "id": 1,
  "author": "John Smith",
  "createdAt": "2024-02-08T17:01:58.295Z",

  "links": {
    "self": "/users#profile"
  }
}

hateos-url-manager's People

Contributors

tkottke90 avatar

Stargazers

Steven Beshensky avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.