Let‘s take a look at the high level requirements. Keep in mind, we are implementing all API elements in both NodeJS and Java/Spring stacks.
We are creating a series of layers that make a clear definition between front end and back end, i.e. an UI element communicating with back end elements via REST. Pretty standard stuff, and I won‘t insult you with a diagram to visualize this as it would be redundant, although it makes managers feel more comfortable.
The front end components will be broken down into multiple projects, as well as the back end projects. We are past the stage in software development where we have to deal with monilithic architecture. To an extend thing need to be modularized, although that comes at a cost of complxity. In purist forms of Microservices, the codebase should be ideally isolated from everything else. There is a cost associated with each decision, and in that case code duplication.
For the purposes of this project, I will attempt a hybrid approach where shared modules will be introduced as appropriate to reduce redundant efforts.
I have spent most of my recent years in the React space. It is a common framework for development, arguably within the top handfull as of 2024. There is a lot of talent out there in this space, a lot of wide support. Angular would be a second choice in my opinion, I love them both, it is just a convenience thing where I am implementing the UI in React, and to be honest I would love to do the same in Angular, however with the time investment, I just don‘t have it, I might attempt this at a future point.
The devil is in the details, as always, so let‘s take a pass on the UI requirements
Because we are dealing with dual implementations of an API backend, the implementation details will be more detailed in the Node API approach as it is
I decided to roll with a bare bones CSS approach here, this would not necessarily be a default decision I would be making if this was a real world production application in an enterprise setting. UI frameworks such as Bootstrap give development efforts a large jumpstart, and provide a level of consistency. My decision was base on just an opportunity to sharpen up my CSS skills, and perhaps a little bit of passion for pain. I would highly encourage anyone attempting this for an ployer to review all options before deciding to take a hand rolled approach.
From a Java API implementation perspective, I am using Java and Spring as a framework. This is not really definitive of approach to this as one with think would exist, and on this project I will be using a opinionated approach to an Spring based REST implmentation. This approach is based on real world implementations, and personal experience working hands on with Spring/Pivital architects.
I have observed a lot of implementations across projects. While there are many approaches to implementing this tech stack, I have seen that that clearly fall short. I will try my best to address my approch here.
NodeJS and ExpressJS. There is another decision point where there are many different directions you can go with the tech stack.
This is an interesting topic. At a high level, exceptions should be handled at a high level. In the Java world, we implement this as a Servlet filter, or more specifically via the @ControllerAdvice and @ExceptionHandler annotations, a very elegant solution. With NodeJS we implement via middleware. In either case it is generally the same approach, where exceptions bubble up to a higher level piece of logic dedicated to handling exceptions.
Exceptions are relayed to the client via the appropriate HTTP status code, where additional relevant information is included in the headers. On more than one occasion I have observed exceptions returned as HTTP 200 (Success) code, with a status message returned indicating the success state. This is bad. Client libraries such as HTTP Fetch and Axios have built in mechanisms to handle response codes. The extra lifting on the client‘s end just does not make sense.
By default, we can assume that we need to store data. An RDBMs is a natural fit for this task. I am a big fan of MongDB, it solves a lot of problems, but as in the real world, we pick the right tool for the job. A core requirement for a lot of enterprise systems is the ability to provide reporting functionality, and this approach plays well with this approach.
As per a database implementation, I am intentionally not implying a preference. There can be a large conversation, I am taking an agnostic approach here as to a vendor, but am landing on MySQL as the focus for this project, bascially lets just plug in an RDBMs implementation.
When implementing different deployments for API implementations, there is a potential for communication between independant stacks. Given the requirements for this effort, we are dealing with a client initiated request to spawn off a background async process. We will roll with a messaging based technology (RabbitMQ specifically) to meet this need. Again not a strong opinion point here, we just need a solution in place.
In my opinion, request validation is an important aspect of API implementation. You can group validation efforts into two groups, the first I would describe as field level, where common checks are performed, such as the presence of a required attribute, values within acceptable lengths for string values, values withing ranges for numeric values, etc. The second would be related to more complex business logic, complex at a minimal meaning requiring more effort.
This is redundant as ideally the client would be performing these checks, an RDBMS will automatically enforce them as a low level, and there should be a level of effort applied in the API implementation piece as well. This means that there is value in streamlining the effort to perform validation holds value. Let‘s take a look my approach to addressing initial validation at the API layer.
There are different approaches for attempting this, I tend to find that the concepts of annotations come in handy.
From a Java/Spring perspective, there are more formal approaches to this including JSR-303 (Bean validation). Basically this allows add hints to an object representing a request operations. Concise, effortless validation, which provides the ability to provide a better response to the client when invalid requests are sent.
From a NextJS perspective, things get a little more interesting. What is referred to as ‘Annotations‘ in the Java world have an equivelent in the Javascript universe as ‘Decorators‘. This is not fully or officially finalized yet at this point, but we are dealing with Typescript level, and compiling behind the scenes, I would argue that
For the actual implementation, we will take a hard coded approach for the actual implementation, simply reqiuring a token in the auth header. From the application‘s perspective, the mechanics of the auth mechanism will be abstracted from the actual logic, providing support for ‘pluggable‘ authentication logic.
I have typically been a large fan of Axios (https://www.npmjs.com/package/axios) as is streamlines communications. As a best practice, I typically abstract the lower level details of API calls to a centralized component anyways, so the actual underlying approach/library can be modified at a later point in time.
Recently I have become a large fan of the TanStack useQuery library (https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) for performing requests. I think a key benefit here is that it provides a convenient was to track request state and error conditions, with some pretty cool cahing features.