Building my own NodeJS MVC Framework — Part 4

As we've seen before, our framework is still missing some important pieces. As it has no idea how to handle form data or even cookies, which are substantial information for a working framework to deal with. We need to be able to exchange data besides route paths and what is in our database.

To include this feature, we need to use the power of Middlewares. But we don't even have the code for it. So, let's start designing how our PostsController#create would act to have some ground to design our tool.

Open controllers/PostsController.js and include a new method in it:

We're using a bunch of things we don't have yet. First, we use this.params.title to match our future form data, but this will be empty if we try it. Also, we do this.redirect() for a future redirection, and we don't have that method. But using our ORM data is just fine. If we try to submit a form with this, we will see an error popping up in the terminal.

Let's add our form, create a new file inside views/posts/ named _form.ejs :

Now, open your views/posts/index.ejs add at the bottom add a new <hr /> to put a break line and after include <%- include('_form') %> . This will render our form inside our /posts . Reload your server and see it working. If you submit, no matter what you try, you'll see an error.

Wait a second. Wasn't the error from the controller, right? Yes. We're also missing our route, open your main.js in your root and include Router.post('/posts', 'PostsController#create'); after the show route.

Now, let's create our redirectTo() capabilities. Open your src/BaseController.js and after the run() method add:

Now, this method is simply teaching our framework how to behave when seeing a 301 (redirect) status code. Open Framework.js again and let's replace our _resolveResponse with this:

Here we have replaced our render to behave with a switch first, checking for 3 constant types: 301, 404 and 500, and the default will handle anything we want in the future.
We also render 2 new static files, we need them to be in place for this to work, create a new file at public/500.ejs and add:

Add create public/not-found.ejs and add:

Very simple files to just render what we really want. Now with everything in place, restart your server and see our redirection is working. But, your post will be persisted either way. We have a small problem in our code, our Post beforeSave() hook actually add data to title before validating, this is a problem since we check the presence of the title to validate.
Simply change your models/Post.js prependTitle() code with: if (this.title) this.title = '-- ' + this.title; .

This will be enough. Now, restart your server and see it's working. Our error messages are being correctly displayed in our terminal.

Now. We need to make our middleware to work. Finding out how to solve this issue was really interesting. The way our requests processed and as we have Promises to be done in between, we can't depend only on async and await, we need to use the power of callbacks here.
So, let's imagine our middlewares for a second as a stack of commands to be processed: let middlewares = [code1, code2, code3]; . In our case, we should pick up the first middleware, code1 and run it. But our Framework should not be responsible for finding when it's done. We'll send a new magic method to our middleware called next() . When the middleware code is done, it should call next() telling our framework it should move to the next one, and it'll do. When everything is done, it'll proceed with the previous work we have, calling this._resolveResponse() . Not too complicated, correct?

So, here, update your run() method in Framework.js :

As you can see, in our else {} used to be just our this._resolveResponse() , but now we have our middlewares logic.
First, we put ours const stack as our middlewares. Then we define our let index = 0; meaning what we want to retrieve from our stack first, and we set our layervariable as empty.handleMiddlewares() is the trick to do our select/run/move/stop logic.
Inside handleMiddlewares() we have our magic controller, next() , it assigns layer at the index we want using the later increment, which means it's first assigned at 0 and returning the first middleware we're going to be using.
Next, we just check if the layer is there, if not, it means we can call our _resolveResponse and continue our code as usual and do an early return to stop the code.

With this in place, we should call our layer passing our 3 most important arguments now, request response and next . And after defining next() we call it immediately, initializing our process. And we do the same for handleMiddlewares() . We call it.

Now, this will work just fine. But we don't have any middleware to use, let's create our first one, in src/middlewares/ add a new BodyParser.js .

Here we import StringDecoder that we have installed previously, it'll handle the data encoding for us. And we also import querystring from NodeJS, to allow us to parse our data easily.

In our BodyParser, we receive our 3 arguments, response will not be used here, but we should always expect these 3 arguments within a middleware.
Then we instantiate our decoder at utf-8 and we set a payload as empty.
If request.controller.data.params is empty, we set it as a hash, to assure we won't receive an undefined later when merging our hash.

Now, we're using the skills from NodeJS http . We have a request.on('data', callback) a method which deals with the data we send from a body. We just populate our payload here with our decoder correct data.
And the last trick is another callback for request, request.on('end', callback) . The server receives the data as chunks, and when all chunks are received our request calls end . Here we finish our decoder data, merge our request.controller.data.params and call next() telling our framework this middleware is done.
Notice, this simple BodyParser middleware doesn't handle max size of data chunks and so on, it's not safe, is just for learning purposes. Someone can flood our server and blow up our memory easily without any protection.
If you want to learn more, I strongly advise to check what express uses.

Let's get back to our Framework.js and tell it to use our new middleware.

At the top of our code, after importing nodeStatic , import our middleware: import BodyParser from './middlewares/BodyParser'; . And inside the constructor, change our this._middlewares to this._middlewares = [BodyParser] . As it'll be parsing our code by default. Restart your server and submit your post again, congratulations, we're persisting data!

Some good news. We're almost there! We just have to include 2 more middlewares and work with a new rule I have created for middlewares, an endware logic. It'll allow the middleware to do a post-process when necessary. I've found this interesting to handle cookies, but I'm pretty sure it's not needed as you can solve this way around using the middle field.

Ok, let's include that now, here's the full Framework.js file with the endwares and two new middlewares: CookieParser and FlashParser :

The change is pretty easy, we assign endures: [] to our request.controller in line 43. And _resolveResponse() we add a loop to run all the endwares() at line 93. Not much to say about that, let's create our middlewares to see what's happening, first src/middlewares/CookieParser.js :

Here we're going to use node-cookie to handle parsing and creating the cookies, also as removing the. Inside our CookieParser we define a secret, which will be encoding the cookies to not read any other cookies we have around. And we just parse all cookies we have into let cookies .

cookiesEndWare is responsible to see the modified cookies, and destroying if we use as this.cookies.myCookie = null inside the controller and creating/updating in the case we have the data.

Last we set our request.controller.data we want to handle, and we push our cookiesEndWare into request.controller.endwares . We call next() and move on.

With this our controllers are able to handle cookies, let's try it.
First open your views/posts/index.js and change the first line to: <h3>Posts count: <%= posts.length %> — Cookie counter: <%= counter %></h3> .

And now we need to tell our controller to deal with counter , update your controllers/PostsController.js index method to:

It's also a very simple thing. If the cookie exists, we increment it, if not we set at 1. And we assign it to our template with counter: this.cookies.counter .

Reload your server and go refreshing your /posts page. The cookies are working!

Let's create our last middleware: FlashParser . It'll allow us to change flash messages to be alive for just one request. I've to say it wasn't my best work and is a pretty ugly code. I did some tricks with a cookie (called session) to kill and an alive cookie with the data. The correct way for doing this would be using actually sessions, as express have a bunch of them. This was just a minor effort to have it working, you should not use this in production in any way. If you want to use authentication and more stuff, you should find a good session handler outside cookies, as anyone can open your browser and see your private data very easily.
That said, I won't be explaining this method, just paste it at src/middlewares/FlashParser.js :

Update your public/stylesheets/main.css with:

And finally, update your views/layouts/application.ejs with:

Here we basically added two new classes to add some style for our flash messages success and error, and we just display it in our application.ejs.

Now, to try it, change our controllers/PostsController.js create() method:

Restart your server and see our flash messages are being exchanged and alive for one request.

Congratulations! We're done with our very simple framework. We're still missing all the tests for the framework, but this will be covered in the next article. For now, it's enough, it was a lot and I've learned very interesting things about NodeJS. I hope you have enjoyed as I did.

Thank you for reading until here, see ya!

Leonardo

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store