Skip to Content
Technical Articles
Author's profile photo Lena Hammerer

Full Stack TypeScript App for Cloud Foundry – Sample Repository

This blog post introduces a new sample repository for a full stack TypeScript App built with the SAP Cloud Application Programming Model for Node.js (CAP) and SAPUI5. It covers a project intended for deployment on the SAP BTP Cloud Foundry environment that uses TypeScript. Find out how such a project is structured, run, built, and deployed!

With all the newly released content around TypeScript for UI5 development, I got curious: Is it time to try TypeScript in a UI project myself? Well, I kind of already gave the answer to this away by now. Still, when I initially started out with the idea, I didn’t know how TypeScript and I would get along and what this eventually would turn into. There were lots of lessons to be learned along the way, but in the end, TypeScript won me over. So I created a sample repository for a compact full stack app, where all JavaScript is replaced with TypeScript. 

Let’s look into some of the details. I’ll outline the project structure and focus on the expanded build process required when using TypeScript.

You can view the entire source code of the sample application on GitHub:

Sample App Overview


Full Stack TypeScript App architecture

The diagram above showcases the different parts the Multitarget Application (MTA) consists of. In addition to the full stack app itself, some SAP BTP services are used, SAP Hana Cloud as database and a simple standalone approuter. All of this lives in the Cloud Foundry environment. A quick summary of what each part does:

The project evolves around a simple lecture schedule scenario. Upon that, the data model consists of Courses, Professors, Rooms, and Lectures. Every lecture has a defined start and end time and is held in one of the rooms. An association to a specific course as well as the lecturing professor is possible. This data is now provided through an OData V4 service built with CAP for Node.js. A dedicated service handler implements a custom action, provides validation when creating a new entry, and adds a non-persistent virtual property. Access to the service is restricted via two different user roles: viewer and admin. Only users with the admin role can change the data. All viewers can read the entries.


Lecture Service Entity Data Model

The data for the lecture schedule is displayed with a freestyle SAPUI5 app. Here two different planning calendars are used to display the lectures over time. Filtering for a professor is possible. Creating, modifying, and deleting entries are shown as options for users with the admin role. This can be checked due to expanding the standalone approuter with an additional route to provide information about the user currently accessing the app.

Development closer look – Build Process

Now it’s time to address how the development with TypeScript differs from regular JavaScript coding. 

TypeScript is a superset of JavaScript developed by Microsoft. Essentially it is JavaScript with syntax for types. The idea is to make JavaScript development more efficient by catching mistakes early with the help of its type system. JavaScript is a loosely typed language, which can be a blessing and a curse at the same time. TypeScript, on the other hand, is optionally strongly typed and therefore helps to catch type errors. What would have resulted in a runtime error with JavaScript can be found beforehand with TypeScript static type checking. Moreover, TypeScript brings future JavaScript to coding by enabling features of ES Next for current JavaScript engines.


Difference between TypeScript and JavaScript

TypeScript files, however, cannot be executed. They need to be compiled back to JavaScript before running the code. The build process can be integrated with common build tools, and a linter adds even more options for code checking. Code linting ties right into one of my favorite TypeScript features – enhanced editor integration like code completion.

Some adjustments to the build parameters related to TypeScript are required to build an MTA for Cloud Foundry deployment. The build process needs to include a TypeScript compilation step since the generated JavaScript will be run on Cloud Foundry in the end. Regardless, the actual mta.yaml file does not differ from a regular MTA project. All that changes is the content of the already included build scripts.

CAP Node.js Service using TypeScript

There are only a few things to note when using TypeScript with CAP. First, a tsconfig.json file at the project’s root is needed to define the compiler options. A standard Node.js configuration is sufficient. This file also declares which TypeScript sources to include (everything in /srv here) and where to put the compiled files. Now you are ready to start writing TypeScript instead of JavaScript. The type declarations conveniently come with the @sap/cds package, so no additional installs are needed. One of TypeScript’s great features is the enhanced editor integration, enabling code assistance with type declarations. This is not limited to pure TypeScript development – they can also enable IntelliSense for JavaScript in an editor like Visual Studio Code. 


Code assistance enabled through @sap/cds type definitions

For the limited scope of this sample application, the interfaces to make the CDS model available in TypeScript are manually created. There are community tools to generate types based on CDS definitions to facilitate the process. You can look at this blog post to find out more: Taking CAP to the next level – with TypeScript

Things get more challenging when it comes to the familiar watchrun and build commands. The sample project includes several scripts for TypeScript adjusted commands:

 "start-service:ts": "cds-ts run --with-mocks --in-memory",
 "watch-service:ts": "cds-ts watch --open",
 "build:cf": "npm run build:cds && npm run cleanup:ts && npm run build:ts",
 "build:ts": "tsc",
 "build:cds": "cds build --production",
 "cleanup:ts": "npx rimraf gen/srv/srv/**/*.ts",

For development, cds-ts is available to spare us from compiling TypeScript to JavaScript every time we want to run the app. The command utilizes ts-node instead of the regular node engine. This is also applicable to running in watch mode.

However, this should not be used for production due to performance reasons. Therefore TypeScript code needs to be compiled when building the app for production. The build process now includes a regular cds build, removing the unnecessary TypeScript files from the output and compiling all TypeScript to JavaScript files, which are then added to the build result instead.

SAPUI5 Freestyle App using TypeScript

For UI5 development with TypeScript, a variety of valuable resources is already available and gradually expanded:

Generating a new project with the help of the Yeoman Easy UI5 Generator, using the ui5-typescript-helloworld app as a template, or following the step-by-step guide are all great ways to achieve a setup and structure similar to the new Full Stack TypeScript App sample project.

In this approach, Babel is used for compiling TypeScript code to modern ES6 JavaScript and ultimately to classic UI5 code. For this purpose, the project utilized the Babel plugin transform UI5 from Ryan Murphy and a .babelrc.json file for configuration. This comes with the advantage of enabling modern JavaScript features like classes, modules, promises, and async/await for the UI5 coding. 

Therefore, the final build process includes this transpile step with Babel creating a webapp folder with regular UI5 JavaScript code and afterward a ui5 build to bundle the code in a /dist folder.


UI5 resources processing flow

The sample app includes several scripts to watch, run and build the SAPUI5 app taking care of TypeScript handling. 

 "build:cf": "npm run build:ts && npm run build:ui5:cf",
 "build:ui5:cf": "ui5 build preload --clean-dest --config ui5-deploy.yaml --include-task=generateManifestBundle generateCachebusterInfo",
 "build": "npm-run-all build:ts build:ui5",
 "build:ts": "babel src --out-dir webapp --source-maps inline --extensions \".ts,.js\" --copy-files",
 "build:ui5": "ui5 build --clean-dest",
 "start": "npm-run-all --parallel watch:ts start:ui5",
 "watch:ts": "babel src --out-dir webapp --source-maps inline --extensions \".ts,.js\" --copy-files --watch",
 "start:ui5": "ui5 serve --port 8080 -o index.html",


Round off

Aiming to cover every possible piece of JavaScript, the approuter extension is also implemented in TypeScript. Unfortunately, no type definitions are provided for the @sap/approuter package at the time of writing. Nevertheless, this does not rule out using TypeScript in general since, technically, all correct JavaScript code is naturally valid TypeScript. It only limits the benefit of doing so. This reveals another TypeScript benefit, which comes into play when gradually converting a JavaScript code basis to TypeScript.

Next Steps

Take a look at the sample repository for the Full Stack TypeScript App on GitHub and try out TypeScript for Cloud development yourself!

  1. Clone the project from GitHub:
    git clone
    cd btp-full-stack-typescript-app
  2. Install the dependencies:
    npm install
  3. Run the app locally:
    npm run start-service:ts

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Simon Gaudek
      Simon Gaudek

      Hi Lena,

      very interesting blog.
      Regarding CAP & TypeScript I can recommend my blog. This one also describes how to use cds2types. So you don't have to write the interfaces for the entities yourself, they can be compiled from the CDS definitions.


      Author's profile photo Lena Hammerer
      Lena Hammerer
      Blog Post Author

      Hi Simon,

      Thanks for commenting and pointing to your blog post again. A link to it is also included in the CAP TypeScript section of this post, where I talk about interfaces.


      Author's profile photo Holger Schäfer
      Holger Schäfer

      Hi Lena,

      very nice sum up and i saw you also added content to the FullStack TS Repo.

      Just one idea concerning AppRouter.

      I personally follow the CAP guide concerning TS recommendations -> for productive usage always precompile TS.

      You are using ts-node and typescript as dependency while CAP just uses it as a devDependency.

      I personally would always precompile code before deploying and trying to use as less as possible dependencies inside the productive package. In the past, there has been a lot of scenarios where you even had to decide including node_modules into mta package or not for multiple issue reasons like sqlite throwing build error.

      The comfort of using ts-node and CAP cds-ts watch is greate during development, but i would be to afraid, introducing additional runtime layers like ts-node that posibly produce errors while deploying and staging BTP services.

      Even for costs reason (1 GB BTP runtime = ~70,- Euro/month), i always try to strip down services as much as possible (memory and disk quota).

      But that is only my opinion. Maybe today or in the future, ts-node will be the new standard or node will support it out-of-the-box 😉

      Looking forward for next posts of you!


      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Holger,

      I fully agree.

      You're right with the costs for the BTP CF runtime. But it's good that the disk quota doesn't count there.


      Author's profile photo Lena Hammerer
      Lena Hammerer
      Blog Post Author

      Hi Holger,

      I am with you on that take - precompiling is the way to go for TS. The sample repo has been updated with some adjustments to the AppRouter to reflect that earlier this week.


      Author's profile photo Namasivayam Mani
      Namasivayam Mani

      Hi Gregor Wolf

      I have cloned the repo: SAP-samples / btp-full-stack-typescript-app to check how I can enable my CAP service with Typescript.
      When I try to run the service, it is starting successfully but I am getting the following error, when I open the preview app of any entity: Lectures/Rooms/Courses/Professor.

      "App could not be opened because the SAP UI5 component of the application could not be loaded.

      Failed to load UI5 component for navigation intent "#preview-app" "

      Any I idea how to resolve this?

      Thanks in advance.


      Author's profile photo Jason Muzzy
      Jason Muzzy

      I noticed that the Full Stack TypeScript App on GitHub uses in the service.ts file.  Is there an example of how to use the CQL constructs like SELECT.columns().from().where() as mentioned in the changelogs?  Currently when I use SELECT in a .ts file I get an error stating "Cannot find name 'SELECT'".



      Author's profile photo Merve Gül
      Merve Gül

      Hi Lena,


      Thanks for this informative blog post.

      I wanted to try a cloud foundry deployable basic typescript app from scratch with the help of your shared repo. I was able to successfully build a MTA project with a basic Typescript app and deploy it  to BTP, but for some reason Component.js is not get recognized by the BTP Launchpad service. Would you be able to kindly make a comment onto that? Is there any special build/deployment command for Typescript applications different from CAP application deployment that we suppose to use before/during deployment?


      Thanks in advance,



      failed to load helloworld/Componentjs



      _schema-version: "3.2"
      ID: ui5-ts-approuter
      description: Fiori elements app
      version: 0.0.1
      - name: ui5-ts-approuter-dest-content
        - name: ui5-ts-approuter-destination-service
            content-target: true
        - name: ui5-ts-approuter-repo-host
              name: ui5-ts-approuter-repo-host-key
        - name: ui5-ts-approuter-uaa
              name: ui5-ts-approuter-uaa-key
              - Name: ui5-ts-approuter_repo_host
                ServiceInstanceName: ui5-ts-approuter-html5-srv
                ServiceKeyName: ui5-ts-approuter-repo-host-key
              - Authentication: OAuth2UserTokenExchange
                Name: ui5-ts-approuter_uaa
                ServiceInstanceName: ui5-ts-approuter-xsuaa-srv
                ServiceKeyName: ui5-ts-approuter-uaa-key
              existing_destinations_policy: update
          no-source: true
      - name: ui5-ts-approuter-app-content
        path: .
        - name: ui5-ts-approuter-repo-host
            content-target: true
          build-result: resources
          - artifacts:
            name: ui5tsapprouter
            target-path: resources/
      - name: ui5tsapprouter
        type: html5
        path: app/helloworld
          build-result: dist
          builder: custom
          - npm install
          - npm run build:cf
          supported-platforms: []
      - name: ui5-ts-approuter-uaa
        type: org.cloudfoundry.managed-service
          path: ./xs-security.json
          service: xsuaa
          service-name: ui5-ts-approuter-xsuaa-srv
          service-plan: application
      - name: ui5-ts-approuter-destination-service
        type: org.cloudfoundry.managed-service
            HTML5Runtime_enabled: true
            version: 1.0.0
                existing_destinations_policy: update
                - Name: ui5
                  Type: HTTP
                  ProxyType: Internet
                  Authentication: NoAuthentication
          service: destination
          service-name: ui5-ts-approuter-destination-service
          service-plan: lite
      - name: ui5-ts-approuter-repo-host
        type: org.cloudfoundry.managed-service
          service: html5-apps-repo
          service-name: ui5-ts-approuter-html5-srv
          service-plan: app-host
        deploy_mode: html5-repo


      app/helloworld folder package.json

          "name": "src",
          "version": "1.0.0",
          "description": "UI5 Application: src",
          "author": "merveguel",
          "license": "Apache-2.0",
          "scripts": {
              "build:cf": "npm run build:ts && npm run build:ui5:cf",
              "build:ts": "babel src --out-dir webapp --source-maps inline --extensions \".ts,.js\" --copy-files",
              "build:ui5:cf": "ui5 build preload --clean-dest --config ui5-deploy.yaml --include-task=generateManifestBundle generateCachebusterInfo",
              "build": "rimraf resources mta_archives && mbt build --mtar archive",
              "build:opt": "ui5 build self-contained --clean-dest --all",
              "start": "ui5 serve --port 8080 -o index.html",
              "start:dist": "ui5 serve  --port 8080 -o index.html --config ui5-dist.yaml",
              "ts-typecheck": "tsc --noEmit",
              "lint": "eslint webapp",
              "test": "npm run lint",
              "undeploy": "cf undeploy undefined --delete-services --delete-service-keys --delete-service-brokers",
              "deploy": "cf deploy mta_archives/archive.mtar --retries 1"
          "devDependencies": {
              "@babel/cli": "^7.21.5",
              "@babel/core": "^7.20.12",
              "@babel/plugin-transform-typescript": "^7.21.3",
              "@babel/preset-env": "^7.21.5",
              "@babel/preset-typescript": "^7.21.5",
              "@sap/ui5-builder-webide-extension": "^1.1.9",
              "@sap/ux-ui5-tooling": "^1.9.6",
              "@sapui5/types": "1.114.0",
              "@typescript-eslint/eslint-plugin": "^5.57.1",
              "@typescript-eslint/parser": "^5.57.1",
              "@ui5/cli": "^3.1.2",
              "babel-preset-transform-ui5": "^7.1.4",
              "eslint": "^8.37.0",
              "i": "^0.3.7",
              "mbt": "^1.2.23",
              "npm": "^9.6.7",
              "rimraf": "^3.0.2",
              "typescript": "^5.0.3",
              "ui5-middleware-livereload": "^0.8.2",
              "ui5-task-zipper": "^0.8.2",
              "ui5-tooling-transpile": "^0.5.2"
      Author's profile photo Daniel Dragolea
      Daniel Dragolea

      Hi Lena,

      Maybe you can have a look over the below npm packages, which might be helpful on SAP CAP TS projects

      • CDS-TS-Dispatcher - TypeScript package for handling the events, support for draft
        • Example draft decorators :
          • @OnReadDraft(), @AfterReadDraft(), @OnSaveDraft()
        • Example active entity methods
          • @OnRead(), @AfterRead(), @BeforeRead()
          • and many more ...
      • CDS-TS-Repository - Simplified interface for common database actions
        • Example .create, createMany, update, delete, deleteMany, exists, getLocaleTexts, count, updateLocaleTexts ... and many more 

      Examples how to use the CDS-TS-Dispatcher & CDS-TS-Repository => GitHub