# Migrate from v6
# What's new ?
Topics | Migration note | Issue |
---|---|---|
Use virtual router instead of Express or Koa router | See | #1987 (opens new window) |
Remove componentsScan options and any glob pattern to discover services | See | #1503 (opens new window) |
Use @tsed/engines instead of Consolidate | See | #1503 (opens new window) |
Remove possibility to register twice handler with the same path + http verb | See | #1793 (opens new window) |
Change the default value of jsonMapper.disableUnsecureConstructor to true | See | #1942 (opens new window) |
Remove ConverterService | See | |
Remove export * from @tsed/platform-cache in @tsed/common | See | #1657 (opens new window) |
Remove @tsed/async_hook_context | See | #1860 (opens new window) |
OIDC: use /oidc basePath by default | #1490 (opens new window) | |
Remove deprecated code/decorators/functions in v6 | See |
# Workflow to migrate your app
This graph will help you to migrate your application to the v7. Don't hesitate to give us feedback on github issues (opens new window)!
# Update CLI
If you use Ts.ED CLI on your project, don't forget to update all CLI v3 dependencies to v4!
# Virtual router
Ts.ED replace the Express.Router and Koa.Router by his own Router. In most cases this will be transparent to you as you are using the Ts.ED controllers and Platform API.
# Why ?
This change will make it easier to integrate other frameworks like fastify and Hapi that don't have a nested router.
With Express you can create nested routers to compose your routes. What could be practical in a pure Express Application. With Ts.ED the use is limited or even transparent.
Hapi and Fastify only support a list of routes with full paths.
In fact, it is much simpler for Ts.ED to generate this list of flat routes and then map the routes/handlers to the target framework rather than managing each framework's nested routers! (Koa router is a real nightmare).
# Potential issue
There is a scenario where you may run into problems if you inject PlatformRouter into your controller to dynamically create your routes.
Here is the example:
import {PlatformRouter} from "@tsed/platform-router"; // in v6 import {PlatformRouter} from "@tsed/common";
import {Controller} from "@tsed/di";
@Controller("/calendars")
export class CalendarCtrl {
constructor(router: PlatformRouter) {
router.get("/", this.myMethod.bind(this));
// GET raw router (Express.Router or Koa.Router)
console.log(router.callback()); // v7 removed
console.log(router.getRouter()); // removed
console.log(router.raw); // return PlatformRouter itself intead Express.Router
}
myMethod(req: any, res: any, next: any) {}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Components scan
ComponentsScan is an option to import services using a glob pattern. It's pretty useful when you start new application.
But this option can cause some issues or misunderstood:
- When you change a location for a Controller/Service/Modules, the code isn't discovered as expected. It's normal, they
server have default configuration like
**/services/**/*.ts
. if the code is outside of this pattern, the won't be loaded. - Performance issue. The global pattern have a huge impact on the server bootstrap!
- Isn't compatible with Webpack or any bundler because the code imported dynamically.
- Importing dynamically code using glob pattern in production may be a breach exploitable by hackers to inject code.
This is why I decided to replace the componentsScan by another tools named barrelsby
. The latest CLI version install barrelsby on fresh project. This tool use globpattern to generate an index files (barrel file) with all controllers. This tool isn't invoked a the runtime but before when you build the code or you invoke the NPM script to generates barrels.
It's maybe less magical, but it solves all previous described issues encountered with the componentsScan
.
You can try this tool by generate a project with tsed init .
. In the new project you'll have a new NPM task barrels
to generate barrels files. You can customize easily the barrelsby configuration to generate multiple barrels file at different levels of your project!
Here is the main points about barrelsby usage in a Ts.ED project:
{
"scripts": {
"build": "yarn run barrels && yarn run tsc --project tsconfig.compile.json",
"barrels": "barrelsby --config .barrelsby.json",
"start": "yarn run barrels && tsnd --inspect --ignore-watch node_modules --respawn --transpile-only -r tsconfig-paths/register src/index.ts"
}
}
2
3
4
5
6
7
.barrelsby.json
{
"directory": ["./src/controllers/rest"],
"exclude": ["__mock__", "__mocks__", ".spec.ts"],
"delete": true
}
2
3
4
5
This configuration generate index.ts file with the following contents:
/**
* @file Automatically generated by barrelsby.
*/
export * from "./HelloWorldController";
export * from "./UserController";
2
3
4
5
6
And we can use it as following:
import * as rest from "./controllers/rest";
@Configuration({
mount: {
"/rest": [
...Object.values(rest)
]
}
})
2
3
4
5
6
7
8
9
# Known issue with barrelsby
Actually, Barrelsby doesn't support ESM module. But an issue is already opened here (opens new window) to support it!
# Keep the componentsScan
To facilitate your migration, you can install the @tsed/components-scan
module. This module does exactly the same as what you have in v6
Here is the right way to use the componentsScan
since v6.104.0:
import {$log} from "@tsed/common";
import {importProviders} from "@tsed/components-scan";
import {PlatformExpress} from "@tsed/platform-express";
import {Server} from "./Server";
async function bootstrap() {
try {
const scannedProviders = await importProviders({
mount: {
"/rest": [__dirname + "/**/controllers/**/*.ts"]
},
imports: [__dirname + "/**/services/**/*.ts", __dirname + "/**/protocols/**/*.ts"]
});
const platform = await PlatformExpress.bootstrap(Server, {
...scannedProviders
});
await platform.listen();
process.on("SIGINT", () => {
platform.stop();
});
} catch (error) {
$log.error({event: "SERVER_BOOTSTRAP_ERROR", error});
}
}
bootstrap();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Template Engines
Consolidate is a perfect wrapper to support multiples engines, but It doesn't provide API to adding new Engines. And the package is no longer actively maintained.
Ts.ED Engines provide a set of engines and let you use decorators to implement your custom engine:
import {Engine, ViewEngine} from "@tsed/engines";
@ViewEngine("pug", {
requires: ["pug", "then-pug"] // multiple require is possible. Ts.ED will use the first module resolved from node_modules
})
export class PugEngine extends Engine {
protected $compile(template: string, options: any) {
return this.engine.compile(template, options);
}
protected async $compileFile(file: string, options: any) {
return this.engine.compileFile(file, options);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
By this way, you'll be able to use it with Ts.ED (server or CLI) to compile and render your template.
For example, Ts.ED engines support Vite as View engine!! See more here (opens new window).
Ts.ED engines is already integrated to v6. You can now switch your project to this library and open issues if you have any problems.
For more details about engine configuration with Ts.ED, see our dedicated page.
# Register twice handler for the same route
One of the possibilities given by express.js is to be able to register two routes for the same http path and verb.
For example, you can do that:
app.get("/mypath", myHandler1);
app.get("/mypath", myHandler2);
2
Ts.ED allowed to do the same thing with controllers in v6:
@Controller("/")
class MyController {
@Get("/mypath")
getA() {}
@Get("/mypath")
getB() {}
}
2
3
4
5
6
7
8
But this principle leads to unexpected behavior and causes a regression when using an Error type middleware with UseAfter. This problem is reported in the #1793 (opens new window).
With v7, adding more handlers for the same path and http verb is no longer possible. Also, this behavior is specific to Express and Koa but doesn't seem to be possible with Fastify and Hapi.js.
# JsonMapper options
Introduced recently in v6, the jsonMapper.disableInsecureConstructor
option let you remove an insecure behavior in the deserialize
function used by Ts.ED. This problem is described in #1942 (opens new window) issue.
This security issue is only available if you implement a constructor in your model like this:
class MyModel {
constructor(obj: any = {}) {
Object.assign(this, obj); // potential prototype pollution
}
}
2
3
4
5
This constructor expose you app to a potential prototype pollution hacking!
Since v6, it's recommended to set jsonMapper.disableInsecureConstructor
to true
. In v7, this option is by default to true
,
but you can revert it, in order to allow you the possibility of migrating smoothly your application.
# ConverterService
The ConverterService finally bows out and gives way to its functional version widely used in v6. The migration is quite simple:
Before:
import {ConverterService} from "@tsed/common";
import {Inject, Injectable} from "@tsed/di";
@Injectable()
class MyClass {
@Inject()
mapper: ConverterService;
doSomething1(client: Client): any {
return this.mapper.serialize(client, {type: Client});
}
doSomething2(client: any): Client {
return this.mapper.deserialize(client, {type: Client});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
After (v6/v7):
import {serialize, deserialize} from "@tsed/json-mapper";
import {Inject, Injectable} from "@tsed/di";
@Injectable()
class MyClass {
doSomething1(client: Client): any {
return serialize(client, {type: Client});
}
doSomething2(client: any): Client {
return deserialize(client, {type: Client});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# Platform Cache
In v6, all decorators from @tsed/platform-cache
are re-exported under @tsed/common
. But most of the time you don't use this part.
In v7, you need to install @tsed/platform-cache
as a dependency of your project and make the following replacements:
import {Injectable} from "@tsed/di";
- import {UseCache} from "@tsed/common";
+ import {UseCache} from "@tsed/platform-cache";
@Injectable()
export class MyService {
@UseCache()
method() {}
}
2
3
4
5
6
7
8
9
# Async hook context
The @tsed/async-hook-context
was introduced in v6 to support a context Request injectable everywhere in your code.
The module is now stable and is directly integrated into the @tsed/di
module. The changes are as follows:
- import {Injectable, Controller} from "@tsed/di";
- import {InjectContext} from "@tsed/async-hook-context";
+ import {Injectable, Controller, runInContext, InjectContext} from "@tsed/di";
import {PlatformContext} from "@tsed/common";
@Injectable()
export class CustomRepository {
@InjectContext()
protected $ctx?: PlatformContext;
async findById(id: string) {
this.ctx?.logger.info("Where are in the repository");
return {
id,
headers: this.$ctx?.request.headers
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
And in your test:
- import {runInContext} from "@tsed/async-hook-context";
+ import {runInContext} from "@tsed/di";
import {PlatformContext} from "@tsed/common";
import {CustomRepository} from "./CustomRepository";
describe("CustomRepository", () => {
beforeEach(() => PlatformTest.create());
afterEach(() => PlatformTest.reset());
it("should run method with the ctx", async () => {
const ctx = PlatformTest.createRequestContext();
const service = PlatformTest.get<CustomRepository>(CustomRepository);
ctx.request.headers = {
"x-api": "api"
};
const result = await runInContext(ctx, () => service.findById("id"));
expect(result).toEqual({
id: "id",
headers: {
"x-api": "api"
}
});
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Removed packages
The following Ts.ED packages are removed:
@tsed/graphql
: Use@tsed/typegraphql
instead.@tsed/seq
: Use the@tsed/logger-seq
instead.@tsed/aws
: Use@tsed/serverless-http
instead.
# Other breaking changes
# @tsed/core
- Remove
deepExtends
util. UsedeepMerge
instead.
# @tsed/common
- Remove Node.js 12 support.
- Rename
useCtxHandler
function. UseuseContextHandler
instead. It's an internal feature used by official Ts.ED plugin. - Remove
HttpsServer
symbol and useHttps.Server
fromhttps
module as injectable type instead. - Remove
HttpServer
symbol and useHttp.Server
fromhttps
module as injectable type instead. - Remove
PropertyMetadata
. UseJsonEntityStore
instead from@tsed/schema
. - Remove
UseParam(paramType, options)
signature. UseUseParam({paramType: 'BODY', ...options})
signature instead. - Remove
ResponseData
decorator. Use@Context() $ctx: Context
then$ctx.data
. - Remove
EndpointInfo
decorator. Use@context() $ctx: Context
then$ctx.endpoint
to retrieve endpoint information. - Remove
Remove GlobalAcceptMimesMiddleware
andAcceptMimesMiddleware
. UsePlatformAcceptMimesMiddleware
instead.
# @tsed/di
- Remove
registerFactory
. UseregisterProvider
instead. - Remove
loadInjector
util. Useinjector.load(container, module)
instead.
# @tsed/schema
- Remove
IAuth
interface onAuthOptions
. Use@Security()
and@Returns()
instead to configuration authorization option for swagger documentation.
# @tsed/platform-middlewares
- Remove
IMiddleware
. UseMiddlewareMethods
instead.
# @tsed/platform-express
- Remove
CaseSensitive
,MergeParams
,RouterSettings
andStrict
decorators. Those decorators no longer make sense with Ts.ED's Virtual Router.
# @tsed/components-scan
- Remove importComponent function.
# @tsed/oidc-provider
- Default value for
oidc.path
is/oidc
.
# @tsed/passport
- Remove
IProtocol
. UseProtocolMethods
instead.
# @tsed/mikro-orm
- Remove
MikroOrmRegistry.closeConnections()
. UseMikroOrmRegistry.clear()
instead. - Remove
MikroOrmRegistry.createConnection()
. UseMikroOrmRegistry.register()
instead. - Remove
DBContext.getContext()
. UseDBContext.entries()
instead. - Remove
TransactionOptions.connectionName
property. UseTransactionOptions.contextName
instead. - Remove
@Connection
decorator. Use@Orm
instead.
# @tsed/mongoose
- Remove
MongooseVirtualRefOptions.type
. Use RemoveMongooseVirtualRefOptions.ref
instead.
# @tsed/terminus
- Remove BeforeShutdown, OnSignal, OnShutdown, OnSendFailureDuringShutdown decorators. Use followings instead
$beforeShutdown
,$onSignal
,$onSignal
,$onShutdown
or$onSendFailureDuringShutdown
.
Last Updated: 10/24/2024, 6:33:40 AM
Other topics
- Session & cookies
- Passport.js
- Keycloak
- Prisma
- TypeORM
- MikroORM
- Mongoose
- GraphQL
- GraphQL WS
- Apollo
- TypeGraphQL
- GraphQL Nexus
- Socket.io
- Swagger
- AJV
- Multer
- Serve static files
- Templating
- Serverless HTTP
- Seq
- OIDC
- Stripe
- Agenda
- Terminus
- Serverless
- Server-sent events
- IORedis
- Vike
- Jest
- Vitest
- Controllers
- Providers
- Model
- JsonMapper
- Middlewares
- Pipes
- Interceptors
- Authentication
- Hooks
- Exceptions
- Throw HTTP Exceptions
- Cache
- Command
- Response Filter
- Injection scopes
- Custom providers
- Lazy-loading provider
- Custom endpoint decorator
- Testing
- Customize 404