# Migrate from v5

# What's new ?

# Platform API

V6 marks a major evolution of the Ts.ED framework. A lot of work has been done on the internal Ts.ED code since v5 in order to prepare the arrival of this new version. This work was mainly oriented on the creation of an abstraction layer between the Ts.ED framework and Express.js.

The v5 introduced the Platform API and the v6 is the confirmation of this API which allows supporting Express.js (opens new window) and Koa.js (opens new window) and many more in the future.

We are glad this work resulted in the creation of the @tsed/platform-express and @tsed/platform-koa.

# Schema and OpenSpec

This release finally adds support for OpenSpec 3 (opens new window) while supporting the previous version Swagger2 (opens new window). The management of OpenSpec is at the heart of the framework as is JsonSchema (opens new window).

All decorators related to the declaration of schema, routes and endpoints are now integrated in a single module @tsed/schema (opens new window). This module has been designed to be used independently of the Ts.ED framework. You can therefore use it for your projects without installing the whole framework!

WARNING

These decorators have moved from @tsed/common/@tsed/swagger to @tsed/schema:

Loading in progress...

# JsonMapper

In the same idea, the convertersService code was taken out of the @tsed/common (opens new window) module to the new @tsed/json-mapper (opens new window) module. It's based on the @tsed/schema (opens new window) module to perform the mapping of your classes to a Plain Object JavaScript object and vice versa.

You can therefore use it for your projects without installing the whole framework!

See also

# Cache

Ts.ED provide now, a unified cache manager solution based on the awesome cache-manager (opens new window).

See our dedicated page on Cache.

# Breaking changes

# Api & Features

  • ServerLoader API has been removed in favor of Platform API. See Platform API.
  • Filter feature has been removed in favor of Pipes.
  • GlobalErrorHandlerMiddleware has been removed in favor of Exception Filters.
  • ConverterService doesn't perform data validation. Validation is performed by @tsed/ajv package or any other validation library.

# Modules

The following modules have been removed:

  • @tsed/testing: Use PlatformTest from @tsed/common.
  • @tsed/multipartfiles: Use MultipartFile from @tsed/common.
  • ts-express-decorators: Use @tsed/common.

# Decorators

The following decorators have been removed:

# @tsed/di

# @tsed/common

# @tsed/typeorm

  • @EntityRepository: Use EntityRepository from typeorm directly.

# @tsed/swagger

Import the following decorators from @tsed/schema:

Loading in progress...
  • @BaseParameter has been removed.
  • @Operation has been removed.
  • @Responses has been removed.
  • @ReturnsArray has been removed. Use Returns from @tsed/schema.

# Classes

# @tsed/common

  • Classes like ArrayConverter, SetConverter, etc. are replaced by their equivalents ArrayMapper , SetMapper , etc. These classes cannot be injected to another provider.

# Migration guide

# ServerLoader to Platform API

All changes related to Platform API and how to migrate the Server on this new API, are described on a dedicated page. We encourage you to browse the entire page to migrate from v4/v5 to v6.

See our Platform API documentation page.

# Inject service in the Server

With the ServerLoader API in v4/5, injecting a provider can be done as follows:

import {ServerLoader, ServerSettings} from "@tsed/common";
import {MyService} from "./services/MyService";

@ServerLoader({})
export class Server extends ServerLoader {
  $beforeRoutesInit() {
    const myService = this.injector.get<MyService>(MyService);

    myService.getSomething();
  }
}
1
2
3
4
5
6
7
8
9
10
11

Now Platform API, the Server class is considered as a Provider . It means that you can use decorator like Constant and Inject to get any configuration, provider or service from the DI registry.

import {Configuration} from "@tsed/common";
import {Inject} from "@tsed/di";
import {MyService} from "./services/MyService";

@Configuration({})
export class Server {
  @Inject()
  myService: MyService;

  $beforeRoutesInit() {
    this.myService.getSomething();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# SendResponseMiddleware & PlatformResponseMiddleware to ResponseFilter

Breaking changes

  • SendResponseMiddleware has been removed.
  • PlatformResponseMiddleware has been removed.

To reach cross compatibility and performance with Express.js and Koa.js, the SendResponseMiddleware (aka PlatformResponseMiddleware in latest v5 version) has been removed.

Now, when a request is sent to the server all middlewares added in the Server, Controller or Endpoint with decorators will be called while a response isn't sent by one of the handlers/middlewares in the stack.

For each executed endpoints and middlewares, Platform API stores the return value to the Context . We have two scenarios:

  1. If data is stored in the Context object, the response will be immediately sent to your consumer after the UseAfterEach middleware (if present).
  2. If no data is stored in the Context object, the call sequence middlewares continue to the next endpoint (if present) or to the UseAfter then Global middlewares until data is returned by a handler.

By removing this middleware, it isn't possible for the v5 application to override the middleware and change the response format before sending it to the consumer.

The Response Filter, implemented in v6.1.0, allows this possibility again but in a more elegant way by using the @ResponseFilter decorator and a class.

    WARNING

    The wrapper won't be documented in your generated swagger.json!

    TIP

    See all possibilities of this new feature on its dedicated page Response Filter.

    # GlobalErrorHandler to Exception Filter

    Breaking changes

    • CustomGlobalErrorHandlerMiddleware has been removed.
    • Default Exception Filter returns a Json object to your consumer.

    To fix that, remove the line where you add you custom middleware in the server:

    class Server {
      $afterRoutesInit() {
        this.app.use(CustomGlobalErrorHandlerMiddleware); // remove this
      }
    }
    
    1
    2
    3
    4
    5

    TIP

    To migrate your CustomGlobalErrorHandlerMiddleware to create an exception filter, see our Exception Filter documentation page to know what is the appropriate implementation for your usecase.

    Exception Filter uses the Catch decorator to catch a specific instance error. For example, if you want to catch an Http exception, you have to provide the generic Exception class to the decorator as follows:

    import {PlatformContext, ResponseErrorObject} from "@tsed/common";
    import {Catch, ExceptionFilterMethods} from "@tsed/platform-exceptions";
    import {Exception} from "@tsed/exceptions";
    
    @Catch(Exception)
    export class HttpExceptionFilter implements ExceptionFilterMethods {
      catch(exception: Exception, ctx: PlatformContext) {
        const {response, logger} = ctx;
        const error = this.mapError(exception);
        const headers = this.getHeaders(exception);
    
        logger.error({
          error
        });
    
        response.setHeaders(headers).status(error.status).body(error);
      }
    
      mapError(error: any) {
        return {
          name: error.origin?.name || error.name,
          message: error.message,
          status: error.status || 500,
          errors: this.getErrors(error)
        };
      }
    
      protected getErrors(error: any) {
        return [error, error.origin].filter(Boolean).reduce((errs, {errors}: ResponseErrorObject) => {
          return [...errs, ...(errors || [])];
        }, []);
      }
    
      protected getHeaders(error: any) {
        return [error, error.origin].filter(Boolean).reduce((obj, {headers}: ResponseErrorObject) => {
          return {
            ...obj,
            ...(headers || {})
          };
        }, {});
      }
    }
    
    1
    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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    # Converter to JsonMapper

    The @tsed/json-mapper package is now responsible to map a plain object to a model and a model to a plain object.

    It provides two functions serialize and deserialize to transform objects depending on which operation you want to perform. It uses all decorators from @tsed/schema package and TypeScript metadata to work.

    Breaking changes

    • The @Converter decorator has been removed in favor of JsonMapper decorator.
    • Classes like ArrayConverter, SetConverter, etc. are replaced by their equivalents Types mapper: ArrayMapper , SetMapper , etc.
    • Type mapper classes are no longer injectable services.
    • ConverterService is always available and can be injected to another provider, but now, ConverterService doesn't perform data validation. Validation is performed by @tsed/ajv package or any other validation library.
    • PropertyDeserialize and PropertySerialize have been removed and replaced by OnDeserialize and OnSerialize .
    • Methods signatures of Type mapper (like ArrayConverter) have changed.

    Here is the ArrayConverter as implemented in v5:

    import {Converter, IConverter, IDeserializer, ISerializer} from "@tsed/common";
    
    @Converter(Array)
    export class ArrayConverter implements IConverter {
      deserialize<T>(data: any, target: any, baseType: T, deserializer: IDeserializer): T[] {
        return [].concat(data).map((item) => deserializer!(item, baseType));
      }
    
      serialize(data: any[], serializer: ISerializer) {
        return [].concat(data as any).map((item) => serializer(item));
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    Now, The new ArrayMapper as implemented in v6:

    import {JsonMapper, JsonMapperCtx, JsonMapperMethods} from "@tsed/json-mapper";
    
    @JsonMapper(Array)
    export class ArrayMapper implements JsonMapperMethods {
      deserialize<T = any>(data: any, options: JsonMapperCtx): T[] {
        return [].concat(data).map((item) => options.next(item));
      }
    
      serialize(data: any[], options: JsonMapperCtx): any {
        return [].concat(data as any).map((item) => options.next(item));
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    To help you migrate your custom mapper, here is a small table of equivalent points between v5 and v6:

    V5 V6 Description
    Converter JsonMapper The decorator has the same behavior. You can use JsonMapper to override an existing mapper.
    deserializer/serializer options.next(item) Call the next function to deserialize/serialize object.
    target/baseType JsonMapperCtx.type/JsonMapperCtx.collectionType The types of the object and the collection.

    See also

    See our JsonMapper documentation page for details on Type mapper.

    # Any with BodyParams

    Use any as type for a body parameter, will be translated as type Object by typescript. It means, if you use @tsed/ajv, now, the validation will fail if you send a different type as expected in the payload.

    import {BodyParams} from "@tsed/platform-params";
    import {Post} from "@tsed/schema";
    import {Controller} from "@tsed/di";
    import {Any} from "@tsed/schema";
    
    @Controller("/any")
    export class AnyCtrl {
      @Post()
      updatePayload1(@BodyParams() payload: any): any {
        // accept only Object
        // breaking change with v5
        console.log("payload", payload);
    
        return payload;
      }
    
      @Post()
      updatePayload2(@BodyParams() @Any() payload: any): any {
        // accept all types
        console.log("payload", payload);
    
        return payload;
      }
    
      @Post()
      updatePayload3(@BodyParams() payload: any[]): any {
        // accept array of any types
        console.log("payload", payload);
    
        return payload;
      }
    }
    
    1
    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
    30
    31
    32

    Add Any decorator to fix the issue.

    # Enum with BodyParams

    Use an enum as default value for a body parameter (or query parameter), will be translated as type Object by typescript. It means, if you use @tsed/ajv, now, the validation will fail if you send a different type as expected in the payload.

    import {QueryParams} from "@tsed/platform-params";
    import {Post} from "@tsed/schema";
    import {Controller} from "@tsed/di";
    import {Enum} from "@tsed/schema";
    
    export enum SearchType {
      PARTIAL = "partial",
      EXTENDED = "extended"
    }
    
    @Controller("/enums")
    export class AnyCtrl {
      // BREAKING CHANGE with v5
      @Post()
      updatePayload1(@QueryParams("type") type = SearchType.PARTIAL): any {
        // type = SearchType.PARTIAL will considered by typescript as any => Object
      }
    
      // WORKS
      @Post()
      updatePayload2(@QueryParams("type") type: SearchType = SearchType.PARTIAL): any {}
    
      // Add validation
      @Post()
      updatePayload3(@QueryParams("type") @Enum(SearchType) type: SearchType = SearchType.PARTIAL): any {}
    }
    
    1
    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

    # Status decorator

    Status decorator from @tsed/schema is different from @tsed/common:

      # Returns decorator

      Returns decorator from @tsed/schema is different from @tsed/common:

        # ReturnsArray decorator

        ReturnsArray is deprecated and will be removed in v7. You have to use Returns .

          Last Updated: 2/11/2022, 7:27:21 PM

          Other topics