Modules

This is not specific to TypeScript, but still I would like to talk about it. The way you organize your code and impose boundaries between different parts of your project is very important to how effectively you can make use of it. TypeScript is going to be an important tool to shape those boundaries creating contracts between different parts of your code.

Splitting your code by domains

A domain represents a specific area of functionality within an application, such as user management, payment processing, or product inventory. By organizing your code around domains, you can reason about it better.
Navigating around a project organized like that is easier because its folder structure is expressive and you know where to put your code or where to find what you need to modify.
Having a core or features or modules folder (or any other name you wanna give it) with a folder for each domain, makes it much easier to know where the login or payment feature is implemented.
If you have experience as a professional software developer you most likely have seen a project that had these three folders at the top level: ‘models’, ‘views’ and ‘controllers’. This was very popular for a while, but in fact, it’s not a great way to organize a project, often causing it to rotten quite quickly.
MVC is a great design pattern, but using it to organize all of your code as a top-level architecture is misusing it. It fails to make your folder structure expressive of what its domains are and it spreads domains along those three different folders instead. MVC was created for dealing with GUI and not for organizing a whole application. That is why working on a big MVC application feels like hammering screws on a plank.

Creating boundaries to external code

One time I went to check on one of those bugs that happen every now and then, but no clear reproduction scenario was available. I debugged it for a bit and checked which libraries it was using, finding a real sus one. Unmaintained for 4 years and full of open issues open on GitHub. I did a quick search on the issue and more people were relating the same thing.
It was clear to me that I needed to refactor the project to remove that library. I was lucky it was a small project with not a lot of lines of code, but I was unlucky in that it was a library that was widely used in that project. It affected every feature and had crept its way into almost every file.
Most libraries should not be used directly, as it will make your project depends on them. Sometimes you might need to swap them for a different one. Either because they have become insufficient technically or because of a business decision in the case of paid external services and libraries.
It is much better to invert the dependency when possible and define how your code expects to, for example, send a push notification to a customer independently from what library is being used to do the chore.
A very basic implementation of that would be to simply define an interface such as:
interface PushManager {
  send(customerID: string, message: string): void;
}
And then create classes that map it to the actual implementation for a specific service.
// serviceA.ts
import { send } from 'serviceA';

export class PushServiceA implements PushManager {
  send(customerID: string, message: string) {
    send(customerID, message);
  }
}

// serviceB.ts
import ServiceB from 'serviceB';

export class PushServiceB implements PushManager {
  service = new ServiceB();
  
  send(customerID: string, message: string) {
    this.service.sendPush({
      identifier: customerID,
      body: message
    });
  }
}
You can see that serviceA and serviceB have different API and way of working to accomplish the same goal. But with this implementation you can swap between them quite easily since your code only needs to know the signature of the PushManager interface and doesn’t need to care about how it is implemented.
I would recommend segregating the code that deals with libraries and external services from your core code where your business logic is implemented. Not all frameworks and libraries reward this practice, so don’t go crazy about it as well. Perfect is the enemy of good.