REST Endpoints Design Pattern

In this post I’ll present a suggested design pattern and implementation for this design pattern using a Node + Express REST API with ES Classes. Personally, I hate writing the same code again and again. It violates the DRY principle and I hate to waste my time and my customers’ time. Being a C++ developer in background, I love a nice class design.

In today’s microservices and web, REST endpoints have become somewhat of the de-facto way to connect services and web applications. There are loads of examples how to create REST endpoints and servers using Node.js and Express 4.0. SOAP, which was popular a while back, has given way to JSON. New technologies like GraphQL have not made it to mainstream yet, so for now we are stuck with REST and JSON.

I haven’t found a tutorial that discusses how to do this using ES6 classes and a good class design. This is what we will cover today.

Rather than building REST endpoints over and over, my concept is to have a base router implement base behavior for the REST endpoint, then have derived classes override such behavior if needed.

We create an Abstract Base Class, with all the default route handlers as static methods. Those will take a request, process it (most likely read / write / delete / update the DB) and return the results. Then the SetupRoutes, will be the glue that binds the static methods to the actual routes. In addition our constructor will take a route name which will be the route path that will be processed.

Then derived classes can either disable certain routes, or override routes as need be, while maintaining the base behaviour, if that is what is needed (for example when wrapping a service, or doing simple DB operations).

Now let’s implement this in JavaScript using Node.js, Express and ES Classes. I’m going to implement this example using MongoDB and Mongoose, but you can use any other DB or service you wish. The Mongoose in this code sample is pretty meaningless, it’s just for the sake of the example.

Then I’ll create the server.js main file (we won’t discuss this in detail, as it’s mostly a node/express server. The one line that’s important to note is require('./routes/index')(server,db); as this will create all the routes for our application).

I like to use automatic glue code, rather thant re-type or build a static array. This way we have the system detect new routes and add them automatically, just by adding a file to a folder.

I’m using require-dir which will include all route handlers. I wanted each route to handle it’s own paths, and not the global paths (I like encapsulation). So as a design decision I made the filename the subroute file.

I then create an instance of the route handler class, passing it a reference to the dbDB (so it can do it’s thing).

setupRoutes() returns a router, which I then connect to our server. I’m building on server.use of the express router , to bind routes to the baseurl. If you adpot this impementation you can always use your own structure.

Next let’s look at the base-router-handler which is the base to all route handlers. It will contain most of the code for any endpoint:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

//routes/base-route-handler.js

'use strict';

constexpress=require('express');

constcoWrapper=require('../utils/expressCoWrapper');

classBaseRouteHandler{

constructor(collectionName,db){

this.db=db;

this.router=newexpress.Router();

this.collectionName=collectionName;

this.collection=this.db[this.collectionName];

this.setupMiddleware();

}

staticvalidateOkResponse(res,foundItems){

if(!foundItems||!foundItems.length){

res.status(404).send('item not found');

returnfalse;

}

returntrue;

}

setupMiddleware(){

// attach any middleware you might need on a route baseis,; can be overriden in subclasses

res.connection.setTimeout(0);// disable server timeout - this may take a while

constresult=yield this.collection.find({});

res.json(allItems);

}catch(err){

res.status(500).send('Internal Error');

throwerr;

}

}

static*postMultiple(req,res,next){

try{

constresult=yield this.collection.update([req.body]);

res.json(result);

}catch(err){

res.status(500).send('Internal Error');

throwerr;

}

}

// eslint-disable-next-line require-yield

static*notImplemented(req,res,next){

res.status(501).send('Not implemented');

}

setupRoutes(){

constself=this;

this.router.route('/:id')

.get(coWrapper(self.constructor.getSingle))

.put(coWrapper(self.constructor.putSingle))

.delete(coWrapper(self.constructor.deleteSingle))

.patch(coWrapper(self.constructor.notImplemented))

.post(coWrapper(self.constructor.notImplemented));

this.router.route('/')

.get(coWrapper(self.constructor.getMultiple))

.post(coWrapper(self.constructor.postMultiple()))

.put(coWrapper(self.constructor.notImplemented))

.patch(coWrapper(self.constructor.notImplemented))

.delete(coWrapper(self.constructor.notImplemented));

returnthis.router;

}

}

module.exports=BaseRouteHandler;

I wanted to use generators, as I like their async / await like structure. So I wrote a co-wrapper file that will handle errors and the generators’ routes correctly, including wrapping with a promise. I do not wish to go into depths explaining it, as it’s not the point of this post. But you can see this file, in the git repo.

Next we create the base constructor, which takes the route name and (?). It creates the binding to a collection / table / service / anything else you want. It also calls the middleware setup; if you wish to bind your route based middleware, you can override this function in derived classes.

Next I go through and create static route handlers for each route. As you can see the route handlers are pretty simple: take json in, perform some DB operation and return the result. In other examples you might have more complex behaviour. The nice thing is the base creates a default behaviour, but by overriding the static methods in dervied classes we can do whatever we wish to do.

Once the baseclass is ready we can now create a real route, that will do something!
Let’s create a ‘route-handlers’ folder inside the ‘routes’ folder and add a file called companies.js.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//routes/route-handlers/companies.js

'use strict';

constBaseRouteHandler=require('../base-route-handler');

classCompaniesRouterextendsBaseRouteHandler{

constructor(db){

super('companies',db);

}

static*putSingle(req,res,next){

yield*super.notImplemented(req,res,next);

}

static*deleteSingle(req,res,next){

yield super.notImplemented(req,res,next);

}

static*postSingle(req,res,next){

// do some code to send an email to the admin, to ask to create multiple new companies

}

}

module.exports=CompaniesRouter;

First look at how easy it was to create a new route. We didn’t need to write even this much code. We could just create the constructor and be done with it, if we wanted the same behaviour as the base class.

I did want to show, though, how easy it is to override the code without requiring much work. The base class provided us with a basic implementation for notImplemented[is “basic” an adjective instead of a specific type of implementation?], which makes it easy to disable routes.

Even adding a route is easy. Just add a handler implementation of your own. Makes it easy to test just the functionality and not have to re-write the same code over and over.