簡體   English   中英

如何將 Typescript、NodeJS 和 Express 應用程序部署到 Heroku

[英]How to deploy a Typescript, NodeJS and Express app to Heroku

我想提供有關此的操作方法,因為我找不到任何完整的信息。 我認為這在 Stackoverflow 文檔中似乎最合適。 然而,它已被淘汰*-) - 日落文檔(*)。

相反,我將把它寫成 StackOverflow 問答。

如何將 Typescript、NodeJS 和 Express 應用程序部署到 Heroku

我創建了一個項目( https://gitlab.com/OehmSmith_Examples/herokumovies ),其中包含一個描述需要完成的工作的自述文件,我將在此處重現該項目。 作為一個良好的 StackOverflow 實踐,我還將在本文底部提供所有代碼的副本。

教程 - 將 Typescript NodeJS Express 應用程序部署到 Heroku

本教程將使用https://amenallah.com/node-js-typescript-jest-express-starter/作為基礎應用程序。 我與該網站或作者沒有任何關系。 我選擇它是因為它簡單且有效。 它也是一個好的 OO Typescript 代碼的例子。

安裝打字稿

大多數教程甚至https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html 中的官方文檔都說要全局安裝打字稿:

 > npm install -g typescript

Heroku 沒有打字稿的全局安裝,因此需要將其保存在本地。 示例項目就是這樣做的:

 > npm i -D nodemon rimraf typescript ts-node ts-jest jest @types/jest @types/node

@類型/節點

如果您將@types/node固定在舊版本上,您將看到類似以下錯誤的內容:

~/AppData/Roaming/nvm/v11.15.0/node_modules/typescript/lib/lib.es2015.iterable.d.ts:41:6 - error TS2300: Duplicate identifier 'IteratorResult'.

41 type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
        ~~~~~~~~~~~~~~

  node_modules/@types/node/index.d.ts:170:11
    170 interface IteratorResult<T> { }
                  ~~~~~~~~~~~~~~
    'IteratorResult' was also declared here.

node_modules/@types/node/index.d.ts:170:11 - error TS2300: Duplicate identifier 'IteratorResult'.

170 interface IteratorResult<T> { }
              ~~~~~~~~~~~~~~

~/AppData/Roaming/nvm/v11.15.0/node_modules/typescript/lib/lib.es2015.iterable.d.ts:41:6
    41 type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
            ~~~~~~~~~~~~~~
    'IteratorResult' was also declared here.


Found 2 errors.

來自TypeScript:重復標識符 'IteratorResult' 因此,您需要更新@types/node版本。 這是我在處理舊代碼時遇到的一個問題,並希望包括對此的討論。

雲服務更新端口

由於原始代碼硬編碼端口 5000,因此將index.ts更改為以下內容:

app.listen(process.env.PORT, () => {
    console.log(`server started on port ${process.env.PORT}`)
});

為了實現這一點,我將 PORT 添加到npm scripts ,包括添加start:dev以便您可以像 Heroku 一樣從編譯的打字稿中運行它。

    "start:dev": "PORT=5000 node dist/index.js",
    "dev": "PORT=5000 nodemon --exec ts-node src/index.ts --watch src",

或者它可以在 .env 文件中設置:

PORT=5000

NPM 依賴項

Heroku 不會安裝開發依賴項(任何其他雲提供商也不會)。 因此,您需要將一些依賴項移動到主塊。 例如,一個NestJS應用程序將這些作為開發依賴項,它們需要被移動:

 @nestjs/cli

虛擬數據

我將此構造函數添加到MoviesApi.ts

constructor() {
    // setup some dummy data
    movies.push({
        name: 'Pirates of the caribbean',
        rating: 8.5
    })
    movies.push({
        name: 'Star Wars: A new hope',
        rating: 8.7
    })
}

赫魯庫

現在部署到 Heroku

  1. 如果您還沒有帳戶,請設置一個帳戶並在 Heroku 上創建一個應用程序
  2. 在您的終端中:

     heroku login heroku create moviesheroku // this needs to be unique
  3. 您可能需要將返回的 git url 添加為遠程(使用git remote -v檢查):

     git remote add heroku <git url>
  4. 查找或搜索構建包(下一步已經指定了我使用的那些):

  5. 添加構建包:

     heroku buildpacks:add zidizei/typescript heroku buildpacks:add heroku/nodejs
  6. 確認構建包:

     heroku buildpacks
  7. 提交到您的本地存儲庫

    git init // if not already done git add --all git ci -m "Initial commit. Test project all setup and should be ready to 'serve' but not yet ready to deploy to heroku"
  8. 運行

    npm dev # OR npm run start:dev # It depends on your npm scripts
  9. 使用郵遞員或類似工具進行測試或從命令行運行:

     curl http://localhost:5000/movies
  10. 測試它是否可以使用npm run build

  11. 更新 npm 腳本,以便在 Heroku 上安裝 ( npm install ) 后,它會在嘗試npm run start之前構建它

    "postinstall": "npm run build" # Depends on your npm scripts
  12. 提交到本地存儲庫:

     git add --all git ci -m "Now it should deploy, build and run on heroku"
  13. 部署到heroku。 它應該建立並啟動。

     git push heroku master
  14. 測試(假設您heroku create的應用程序是moviesheroku - 相應地調整)

     curl https://moviesheroku.herokuapp.com/movies

變化

配置文件

我沒有指定Procfile告訴 Heroku 任何有關該應用程序的信息。 幸運的是,它通過確定這是一個node + npm應用程序來創建自己的默認值。 但是,您可以明確定義它,如果您有多個應用程序或類似應用程序,則需要執行此操作。 您可以添加一個 Procfile 來包含(這是默認設置):

web: npm start

節點和 NPM 版本

Heroku 還默認使用這些的最新版本之一。 您可以在package.json文件的頂層顯式設置版本,例如:

 "engines": {
     "node": "10.x",
     "npm": "6.x"
 },

盡管如果您不指定npm版本,那么 Heroku 將為 node 版本使用合理的默認值。

總結性思考

我只用了幾個小時就搞定了。 我需要解決的主要問題是打字稿必須是本地的,而不是全局的。 和構建包。 PORT 也是一個問題,盡管每個雲提供商都需要使用process.env.PORT所以這對我來說很明顯。

Azure 是一場噩夢,需要幾天時間,但這主要是因為我所在的工作場所堅持使用 Windows 服務器。 說來話長,我就不講了。

AWS 太復雜了。 嘗試了一天后,我沒有得到我工作的實例。 但是我確實需要再試一次。 我嘗試使用的應用程序使用了https://tsed.io/庫。 簡單的 Node / Typescript / Express 應用程序應該很容易完成。

(*) - 文檔的廢棄有點令人驚訝,盡管它發生在 2 年前,我想這不是我使用的東西。 而且我一直認為問答是最容易記錄文檔的地方。

代碼

.gitignore

    node_modules
    dist
    coverage

.jest.config.js

    module.exports = {
        preset: 'ts-jest',
        testEnvironment: 'node'
    };

package.json

    {
      "name": "movies",
      "version": "1.0.0",
      "description": "Example from https://amenallah.com/node-js-typescript-jest-express-starter/ but then modify and / or show steps for how to deploy this Typescript NodeJS Express RESTful app to Heroku.",
      "main": "index.js",
      "scripts": {
        "build": "rimraf dist && tsc",
        "postinstall": "npm run build",
        "start": "node dist/index.js",
        "start:dev": "PORT=5000 node dist/index.js",
        "dev": "PORT=5000 nodemon --exec ts-node src/index.ts --watch src",
        "test": "jest --watch",
        "coverage": "jest --coverage"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "@types/express": "^4.17.2",
        "@types/jest": "^24.0.25",
        "@types/node": "^13.1.2",
        "jest": "^24.9.0",
        "nodemon": "^2.0.2",
        "rimraf": "^3.0.0",
        "ts-jest": "^24.2.0",
        "ts-node": "^8.5.4",
        "typescript": "^3.7.4"
      },
      "dependencies": {
        "body-parser": "^1.19.0",
        "express": "^4.17.1"
      }
    }

tsconfig.json

    {
      "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "outDir": "dist",
        "sourceMap": false,
        "allowSyntheticDefaultImports": true,
        "baseUrl": ".",
        "paths": {
          "*": [
            "node_modules/",
          ],
          "typings/*": [
            "src/typings/*"
          ]
        },
      },
      "include": [
        "src/**/*.ts"
      ],
      "exclude": [
        "src/test/**/*.spec.ts"
      ]
    }

src/api/MoviesApi.ts

    import IResource from "typings/IResource";

    let movies: object[] = []

    export default class MoviesApi implements IResource {
        constructor() {
            // setup some dummy data
            movies.push({
                name: 'Pirates of the caribbean',
                rating: 8.5
            })
            movies.push({
                name: 'Star Wars: A new hope',
                rating: 8.7
            })
        }

        create(data: any): any {
            movies.push(data)
            return data
        }

        findMany(): any[] {
            return movies;
        }
    }

src/test/api/Movies.spec.ts

    import IResource from '../typings/IResource'
    import MoviesApi from '../api/MoviesApi'

    const moviesApi: IResource = new MoviesApi()

    describe('Movies API', () => {
        it('should create a new movie', () => {
            const movieData: object = {
                name: 'Pirates of the caribbean',
                rating: 8.5
            };

            const movie: object = moviesApi.create(movieData);

            expect(movie).toEqual(movieData)
        })
    });

src/typings/IResource/index.d.ts

    export default interface IResource {
        create(data: any): any
        findMany(): any[]
    }

src/index.ts

    import * as express from 'express'
    import * as bodyParser from 'body-parser'

    import MoviesApi from './api/MoviesApi'

    const app = express();
    const moviesApi = new MoviesApi();

    app.use(bodyParser.json());

    app.post('/movies', (req: express.Request, res: express.Response) => {
        res.json(moviesApi.create(req.body))
    });

    app.get('/movies', (req: express.Request, res: express.Response) => {
        res.json(moviesApi.findMany())
    });

    app.listen(process.env.PORT, () => {
        console.log(`server started on port ${process.env.PORT}`)
    });

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM