How I Wrote My Own Framework

5th January 2023

Sean

Having spent many years building software using a variety of PHP MVC frameworks, my favourite being Yii2, combined with jQuery, and then in later years, AngularJS. I started to explore using NodeJS as server-side software to simplify the stack and leverage the speed and always-running nature of NodeJS (when configured). Angular 2 was released in September 2016. While many didn't like the transition to TypeScript, compared to JavaScript for version 1 (renamed to AngularJS), I found the move intriguing. I jumped on board, started learning TypeScript and began building new projects with the ecosystem. At the time, there were very few JavaScript server-side frameworks, let alone TypeScript. So, whilst experimenting with a few open-source frameworks, I also decided one of the best ways to really learn TypeScript was to experiment with writing my own server-side framework.

My framework goals:

  1. Written in TypeScript and leverage Dependency Injection
  2. Several existing frameworks separated out the routing and controllers into different files/folders, which felt cumbersome.
  3. Most frameworks supported MongoDB, and I needed to continue supporting MySQL for existing projects and models required to be active records.

Written in TypeScript and leverage Dependency Injection

Due to already having experience with TypeScript and its use with Angular, I chose to write the framework in TypeScript. I leveraged the experimental support for decorators to create a simple yet powerful Dependency Injector that parsed the constructor parameters and, combined with the 'reflect-metadata' library, can determine the requested providers, and apply them as needed. One challenge that arose for dependency injection was circular dependencies and the Typescript compiler’s inability to determine the requested type at compile time, so a new decorator, called Forward Reference, was created to allow the framework to fetch the type at runtime instead.

As of the current release, the server class is the only aspect of the framework that is not handled through dependency injection. The primary purpose of the class is to bootstrap the dependency injector and create the HTTP (s) request listeners, which means the code base is highly malleable. If a particular aspect does not suit a project, we can simply replace it. For example, if a particular session storage adaptor is not supported out of the box, a quick replacement can be provided to the dependency injector, and that will be used instead.

Several existing frameworks separated out the routing and controllers

During the experimentation of several existing frameworks at the time, the practice of separating out routing and their controllers into separate files was an annoyance. It didn't match my goal of an MVC pattern, and so I once again turned to decorators allowing the controllers to declare the route(s) for their methods. This simplified the code base and helped improve the developer experience in finding methods within controllers for troublesome routes.

Over time, repeated code, such as typical CRUD operations, were abstracted away to the framework, and 'helper' decorators were provided to perform those tasks reliably every time.

All this combined to produce controllers that did what they were intended to do, receive requests, handle the data, and return a response.

Continue supporting MySQL for existing projects and models required to be active records

This was probably the most challenging aspect to get right, and the abstract model class went through several iterations before the current version was finalised. One of the key features I liked most about Yii 2 is the support for active records and strict data validation. I felt I must replicate this thoroughly to ensure the security and usability of the framework. Once again, I leveraged decorators to help build a model and its columns to provide TypeScript with complete typing whilst under the hood adding all the required functionality to create a reliable active record.

The next step was to ensure data processing, such as loading from a network request or saving to the database, would only occur if the current model passed its validation rules. By declaring rules on each model's class definition, the developer can ensure only allowed data is loaded into a model and prior to saving, the model passes those same rules. This help ensures that combined with query parameter usage, common attack vectors are protected against automatically, such as SQL injection. As a default, the framework will revert to security first. Therefore, only columns for which validation rules have been declared will be loaded into the model, and all other data is simply ignored.

There are many more aspects to the framework that have been developed over its lifespan. Each update focuses on improving the developer experience by doing the regular tasks and allowing the focus to be on the more enjoyable tasks. I see it as coming up with solutions to challenging problems set out by our clients whilst maintaining speed, security, and scalability.


Glossary

Framework

- a collection of software libraries that are intended to perform specific tasks. It is used by programmers to build applications and websites

PHP

- a general-purpose scripting language geared toward web development

MVC

- Model View and Controller - a software architectural pattern commonly used for developing user interfaces that divide the related program logic into three interconnected elements

Yii2

- is a generic Web programming framework, meaning that it can be used for developing all kinds of Web applications using PHP.

JavaScript

- computer programming language commonly used to create interactive effects within web browsers.

jQuery

- a lightweight, "write less, do more", JavaScript library. The purpose of jQuery is to make it much easier to use JavaScript on your website.

AngularJS

- a JavaScript framework written in JavaScript

Node.js

- an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a web browser

Stack

- a collection of independent components that work together to support the execution of an application

Angular 2+

- an open-source JavaScript framework to build web applications in HTML and JavaScript

TypeScript

- a free and open-source programming language developed and maintained by Microsoft.

Dependency Injection

- is a technique that uses some of the best programming practices to leverage decoupling amongst code

MongoDB

- an open-source document-oriented database that is designed to store a large scale of data and allows you to work with that data very efficiently

MySQL

- an open-source relational database management system. MySQL stores data in tables made up of rows and columns. Users can define, manipulate, control, and query data using Structured Query Language, more commonly known as SQL.

Constructor

- a function that creates an object, often with parameters to initialise the object with

Reflect-Metadata Library

- enables us to write decorators that will read metadata from the static type and this metadata may affect your JavaScript runtime code

Decorator

- is a programming pattern where you wrap something in something else to change some aspect of the original’s behaviour.

Forward references

- allows the runtime execution to extract type information when this is not known at compile time. (Not the same in all languages, specific to the TypeScript compiler in this case)

Request Listener

- a function that is called each time the server gets a request.

Related posts