How to organize all the model interfaces and types in your TypeScript project
Posted on: 2018-09-26
So many ways, so many good solutions. I do not think you can go very wrong regardless of the approach employed with your types. However, there are some benefits in few patterns that I prefer. As a rule of thumb, I do not want to search far away from my types. While the tool that you are using might provide a shortcut to search by name easily, and I do leverage it most of the time, it is crucial to find types easily when you do not know the name or when you are not sure if it already exists. The reason is mostly to avoid duplication and to ease the introduction of new developers in your project but also to find out if one of your teammates already coded a definition that you could reuse.
First of all, the approach I am using is subjective. It means that you have to take many choices that some people might or not agree with your team. I have worked in systems where all the components (views) were together, and all the test in one folder, and all the model in one folder. This is straightforward but come with the cost that when your application grows that these folders are neverending. It becomes hard to navigate. I also saw folder divided by team, which is okay until organization breakdown the structure and then it becomes a mess. I also seen some separation that works by model but mix all model files are a sibling to your views (in React your .tsx file, in Angular the .html), to your tests, etc. The approach is not bad, but when you start having more than one component or page using the same model you are stuck.
The approach I recommend is to have your source folder divided by domain of business. For example, under your "source" folder, you can have a "UserManagement", a "Products", an "Inventory", a "Shipping" folder etc. The idea is at that point, you do not rely on the technology that you will use, neither how your application will separate the UI. Your user management might have 1 page or 10 pages, it doesn't matter. The choice of these domains is subjective. You could avoid having "Inventory" and put everything under "Products". It is up to you and your team to figure out how fined grained your want these domain folders.
Under each of these domain folders, you can start bringing some specification of your technologies. For example, with React, each of these folder has a "Actions", "ContainerComponents", "Middlewares", "Models", "PresentationComponents" and "Reducers". Once again, you have some freedom. You can keep all your container and presentation into a single "components" folder. I like to divide them because I know that my containers are connected while my presentations are more reusable and not connected to Redux directly. Within each of these folders, I have my Typescript file (.ts or .tsx) as well as my tests. For a long time, I positioned my tests files as a sibling of the source folder, but having them close to the files they are testing is a way to keep them in sight. Because they are always in view, I tend to test more. I also clearly see if a file is not having a test file.
Some types are cross domains. Shared types are also valid for very generic visual components. In that case, I have at the root, inside the source folder a domain called "shared." The cross-domain folder is the one that every domain folder can access. Having a shared folder require to be diligent. It is easy to insert code random code instead of thinking where to locate the file accurately.
If we come back to the type, each domain folder has a model folder which has many files. The separation is not one type, one file. The reason is that it would become uncontrollable. The number of types grows very fast, and it is not rare to have thousands of types, even for a small product. The idea is to divide the file per main entity. For example, a product (interface Product) can have a product category (Interface ProductCategory) and a ProductSize (enum ProductSize). These three types can be in the "Product.ts" file. Again, there is room for subjectivity. Someone could argue that more or less type can be in or out of a file. The important here is to have something that makes sense. I often like to have the main entity, in that case, "Product" to hold every children class, interface, enum, type in the same file. I like having a mapping file "ProductMapping.ts" that manipulate the entity from a normalized structure to a denormalized for example.
At the end of the day, the most important detail is to stay coherent. The overall goal is to find where are a type without searching for a long time.