Handling Complicated Requests In NodeJS,Redis,MongoDB

Description of the problem

Dealing with forms/requests on the web is one of the toughest problems to solve in the modern web, and as developers, we always seek ways to handle it in a more elegant way. But for education purposes most of the tutorials, we read/watch online, teach us how to deal with only simple requests. So that when we stumble upon some complicated request with 20 fields, which should create 5 types of models, we just don’t know what to do and dump all the logic in the controller.

This is a problem with the MVC architecture in general because it does not teach us where to put the complicated logic.

Let’s see some example of such complicated request, which we will be using in the post to address the problems and provide you with my solution.

It again is simplified version of what really is possible, but I think is enough to explain the concept.

Example for a complicated request

Let’s pretend we have an app where we have vehicles and we can create requests for parts for them, so someone can send us the needed parts. Here is the Object that we have:

  • Vehicle – description of our car/motorcycle
  • Request – Links the parts to vehicle and author
  • Part – Description of the part needed, each request can contain more that one part
public function store(IlluminateRequest $request)
{
$requestData = $request->validate([
'name' => ['required', 'string', 'min:3'],
'vehicle' => ['required', 'numeric', 'exists:vehicles,id'],
'parts' => ['required', 'array'],
'parts.*.name' => ['required', 'string', 'min:2'],
'parts.*.description' => ['required', 'string', 'min:6'],
'parts.*.oem_number' => ['nullable', 'string', 'min:6'],
]);

$partsRequest = Request::create([
'name' => $requestData['name'],
'vehicle_id' => $requestData['vehicle'],
'user_id' => $request->user()->id,
]);

$parts = array_map(function ($partData) {
return Part::make($partData);
}, $requestData['parts']);

$partsRequest->parts()->saveMany($parts);

return redirect()->route('requests.index');
}

After we saw the code and understand the domain let’s explore some methods to simplify it.

TL;DR: You can jump straight to Form requests to the rescue if you don’t want to read my other solutions and what I think about them.

Possible solutions which are not always The Solution (Private methods, Service classes, Commands)

Long and hairy controller methods

This is always a valid approach to do the work, especially when you don’t plan to revisit this method or controller, but since you are reading this article, you think you need a better approach, so let’s continue with the other options.

Private methods in the controller

This is maybe the best place to start. I like this approach because it keeps the logic in one class even though this is the controller. But we have some problems with this approach:

  • the number of controller methods grow, which might be a problem, when we have 4-5 or more public methods and try to create some private ones for them
  • as these are controller methods they do not have access to the request data, as it belongs to the request object, and we should pass it from method to method. This makes such methods more like plain functions, which is always something to consider when deciding where to put a method.

Create a Dedicated Service classes

The Service classes are something like an extension to the MVC architecture in some frameworks, but I personally don’t like them, because they are just one more buzzword. Don’t know where to put the logic, dump it into a Service Class, or worse, create a Service Class for each controller method.
Another problem with service classes is when you need to return a view from the controller with more than one variable/model/data source. In this case, we should return may be an associative array from the service class (which is practically the response), which in my opinion is a bit problem, because the service class takes the responsibility of the controller.
Of course, if we use them wisely they might be beneficial in some cases because we will have something like the approach with private methods in the controller, but without the problems. If we pass the request to the constructor, then we will be able to use its data in all the private methods of the service class, which solves the problems outlined in using private methods.
In our particular example, this approach might just do the job perfectly, but I think we can think of a better solution.

Commands

The problem with the whole Commands for saving Repositories for fetching concept, to me is that it is too complicated and overwhelming. In most of the cases simple Model::create([…]) and Model::where(…)->get() work just fine.
Of course, the whole CQRS architecture has its place under the sun, but I definitely don’t think, we should go for it without really needing it.

Form requests to the rescue

As we know NodeJS,Redis,MongoDB provides us with Form Request objects, which can be automatically injected in the controller method, and handle the validation and authorization logic of the request, so this can simplify our code a bit. The thing I don’t like here is, that we basically move the array of rules in another class and the validate method is called automatically for us, which is not enough to me. I want the form request to earn its existence. And also the ugly part of the work (the remapping of request fields to model fields) is still in the controller, which is really the thing, that bugs me.

public function store(CreatePartsRequest $request)
{
$partsRequest = Request::create([
'name' => $requestData['name'],
'vehicle_id' => $requestData['vehicle'],
'user_id' => $request->user()->id,
]);

$parts = array_map(function ($partData) {
return Part::make($partData);
}, $requestData['parts']);

$partsRequest->parts()->saveMany($parts);

return redirect()->route('requests.index');
}

So one thing we can do here is to move the mapping part of the code into some descriptive methods on the Form Request so it finally has more work to do to justify its existence and the code becomes much cleaner in my eyes.

The controller code

public function store(CreatePartsRequest $request)
{
$partsRequest = Request::create($request->requestParams());
$parts = $request->parts()->map([Part::class, 'make']);
$partsRequest->parts()->saveMany($parts);

return redirect()->route('requests.index');
}

The form request code

public function rules()
{
return [
'name' => ['required', 'string', 'min:3'],
'vehicle' => ['required', 'numeric', 'exists:vehicles,id'],
'parts' => ['required', 'array'],
'parts.*.name' => ['required', 'string', 'min:2'],
'parts.*.description' => ['required', 'string', 'min:6'],
'parts.*.oem_number' => ['nullable', 'string', 'min:6'],
];
}

public function requestParams()
{
return [
'name' => $this->get('name'),
'vehicle_id' => $this->get('vehicle'),
'user_id' => $this->user()->id,
];
}

public function parts()
{
return collect($this->get('parts', []));
}

With this approach we can handle transformations between HTML forms and DB/models format, switch between conventions (camelCase in js to snake_case in PHP/DB), selecting database records to fill some missing data, extracting other request info, or any other data when needed.
Last but not least, I don’t recommend this approach for each form on your project. Use it only when you need to store more models or have complicate request data massaging.
I even don’t recommend using NodeJS,Redis,MongoDB’s Form Request classes just for validation, do it straight in the controller.