简体   繁体   English

如何将 Typescript、NodeJS 和 Express 应用程序部署到 Heroku

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

I wanted to provide a howto on this as I couldn't find any complete information.我想提供有关此的操作方法,因为我找不到任何完整的信息。 I thought that this seemed most appropriate in Stackoverflow documentation.我认为这在 Stackoverflow 文档中似乎最合适。 However it has been sunsetted *-) - Sunsetting Documentation (*).然而,它已被淘汰*-) - 日落文档(*)。

Instead I will write this as a StackOverflow Q&A.相反,我将把它写成 StackOverflow 问答。

How to deploy a Typescript, NodeJS and Express app to Heroku如何将 Typescript、NodeJS 和 Express 应用程序部署到 Heroku

I created a project ( https://gitlab.com/OehmSmith_Examples/herokumovies ) that includes a README describing what needs to be done and I will reproduce that here.我创建了一个项目( https://gitlab.com/OehmSmith_Examples/herokumovies ),其中包含一个描述需要完成的工作的自述文件,我将在此处重现该项目。 As a good StackOverflow practice I will also provide a copy of all the code at the bottom of this post.作为一个良好的 StackOverflow 实践,我还将在本文底部提供所有代码的副本。

Tutorial - Deploy Typescript NodeJS Express app to Heroku教程 - 将 Typescript NodeJS Express 应用程序部署到 Heroku

This tutorial will work from https://amenallah.com/node-js-typescript-jest-express-starter/ as the base app.本教程将使用https://amenallah.com/node-js-typescript-jest-express-starter/作为基础应用程序。 I have no affiliation with that site or the author.我与该网站或作者没有任何关系。 I chose it as it is simple and works.我选择它是因为它简单且有效。 It is also an example of good OO Typescript code.它也是一个好的 OO Typescript 代码的例子。

Install typescript安装打字稿

Most tutorials or even the official documentation in https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html say to install typescript globally:大多数教程甚至https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html 中的官方文档都说要全局安装打字稿:

 > npm install -g typescript

Heroku doesn't have a global install of typescript so it needs to be kept locally. Heroku 没有打字稿的全局安装,因此需要将其保存在本地。 The example project does just this:示例项目就是这样做的:

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

@types/node @类型/节点

In the case you have pinned your @types/node at an older version you will see something like this error:如果您将@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.

From TypeScript: Duplicate identifier 'IteratorResult' .来自TypeScript:重复标识符 'IteratorResult' And as per that you need to update your version of @types/node .因此,您需要更新@types/node版本。 This was a problem I struck as I was working with older code and wanted to include this discussion of it.这是我在处理旧代码时遇到的一个问题,并希望包括对此的讨论。

Update port for cloud service云服务更新端口

Change the index.ts to be the following instead since the original code hard-coded port 5000:由于原始代码硬编码端口 5000,因此将index.ts更改为以下内容:

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

To allow for this I have added the PORT to the npm scripts , including adding a start:dev so you can run it like Heroku does from the compiled typescript.为了实现这一点,我将 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",

Or it can be set in a .env file:或者它可以在 .env 文件中设置:

PORT=5000

NPM dependencies NPM 依赖项

Heroku will NOT install dev dependencies (neither will any other cloud provider). Heroku 不会安装开发依赖项(任何其他云提供商也不会)。 Therefore you need to move some dependencies to the main block.因此,您需要将一些依赖项移动到主块。 For example, a NestJS application has these as Dev Dependencies and they need to be moved:例如,一个NestJS应用程序将这些作为开发依赖项,它们需要被移动:

 @nestjs/cli

Dummy data虚拟数据

I added this constructor to the MoviesApi.ts :我将此构造函数添加到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赫鲁库

Now deploy to Heroku现在部署到 Heroku

  1. Setup an account if you don't already have one and create an app on Heroku如果您还没有帐户,请设置一个帐户并在 Heroku 上创建一个应用程序
  2. In your terminal:在您的终端中:

     heroku login heroku create moviesheroku // this needs to be unique
  3. You may need to add the returned git url as a remote (check with git remote -v ):您可能需要将返回的 git url 添加为远程(使用git remote -v检查):

     git remote add heroku <git url>
  4. Lookup or search for buildpacks with (the next step already specifies those I use):查找或搜索构建包(下一步已经指定了我使用的那些):

  5. Add buildpacks:添加构建包:

     heroku buildpacks:add zidizei/typescript heroku buildpacks:add heroku/nodejs
  6. Confirm buildpacks:确认构建包:

     heroku buildpacks
  7. Commit to your local repository提交到您的本地存储库

    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. Run with运行

    npm dev # OR npm run start:dev # It depends on your npm scripts
  9. Test with postman or similar or run this from the command-line:使用邮递员或类似工具进行测试或从命令行运行:

     curl http://localhost:5000/movies
  10. Test that it transpiles with npm run build测试它是否可以使用npm run build

  11. Update the npm scripts so that after installation ( npm install ) on Heroku, it will build it before attempting to npm run start更新 npm 脚本,以便在 Heroku 上安装 ( npm install ) 后,它会在尝试npm run start之前构建它

    "postinstall": "npm run build" # Depends on your npm scripts
  12. Commit to local repository:提交到本地存储库:

     git add --all git ci -m "Now it should deploy, build and run on heroku"
  13. Deploy to heroku.部署到heroku。 It should build and start up.它应该建立并启动。

     git push heroku master
  14. Test (assuming the app you heroku create d is moviesheroku - adjust accordingly)测试(假设您heroku create的应用程序是moviesheroku - 相应地调整)

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

Variations变化

Procfile配置文件

I haven't specified a Procfile telling Heroku anything about the app.我没有指定Procfile告诉 Heroku 任何有关该应用程序的信息。 Fortunately it creates its own defaults by determining that this is a node + npm app.幸运的是,它通过确定这是一个node + npm应用程序来创建自己的默认值。 However you can explicitly define this and will need to perform this action if you have multiple apps or similar.但是,您可以明确定义它,如果您有多个应用程序或类似应用程序,则需要执行此操作。 You could add a Procfile to contain (this is the default):您可以添加一个 Procfile 来包含(这是默认设置):

web: npm start

Node and NPM version节点和 NPM 版本

Heroku also defaults to using one of the most recent versions of these. Heroku 还默认使用这些的最新版本之一。 You could explicitly set the versions at the top-level in the package.json file like:您可以在package.json文件的顶层显式设置版本,例如:

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

Although if you don't specify a npm version then Heroku will use a sensible default for the version of node.尽管如果您不指定npm版本,那么 Heroku 将为 node 版本使用合理的默认值。

Concluding thoughts总结性思考

I had this going in only a couple hours.我只用了几个小时就搞定了。 The main issues I needed to work out is that typescript has to be local, not global.我需要解决的主要问题是打字稿必须是本地的,而不是全局的。 And the buildpacks.和构建包。 The PORT is an issue also though every cloud provider requires the use of process.env.PORT so this was obvious to me. PORT 也是一个问题,尽管每个云提供商都需要使用process.env.PORT所以这对我来说很明显。

Azure was a nightmare and took days, but that was mainly because the workplace I was at insisted on using Windows servers. Azure 是一场噩梦,需要几天时间,但这主要是因为我所在的工作场所坚持使用 Windows 服务器。 Long story and I won't go in to it.说来话长,我就不讲了。

AWS was so convoluted. AWS 太复杂了。 I didn't get the instance I had working after trying for a day.尝试了一天后,我没有得到我工作的实例。 however I do need to try again.但是我确实需要再试一次。 The app I was trying used the https://tsed.io/ library.我尝试使用的应用程序使用了https://tsed.io/库。 Simple Node / Typescript / Express apps should work out quite easily.简单的 Node / Typescript / Express 应用程序应该很容易完成。

(*) - the sunsetting of documentation was a little bit surprising though given it happened over 2 years ago I guess it wasn't something I used. (*) - 文档的废弃有点令人惊讶,尽管它发生在 2 年前,我想这不是我使用的东西。 And I always thought that the Q&A was the easiest place for documentation.而且我一直认为问答是最容易记录文档的地方。

Code代码

.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