NestJs Serverless架构下SendGrid.setApiKey is not a function错误求助
问题描述
本地运行NestJS的sendgrid.service.ts时,SendGrid.setApiKey可正常设置密钥并发送邮件,但部署到AWS Lambda及Vercel时,均出现SendGrid.setApiKey is not a function错误。此前曾遇到bcrypt包在无服务器环境的类似问题,目前已卡数日,寻求解决建议。
相关代码配置
sendgrid.service.ts (路径: src/auth/services)
import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as SendGrid from '@sendgrid/mail'; @Injectable() export class SendgridService { constructor() { SendGrid.setApiKey(process.env.SEND_GRID_KEY); } async send(mail: SendGrid.MailDataRequired) { const transport = await SendGrid.send(mail); return transport; } }
package.json
{ "name": "api", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.4.0", "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.4.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.4.0", "@nestjs/platform-socket.io": "^9.4.0", "@nestjs/schedule": "^2.2.1", "@nestjs/typeorm": "^9.0.1", "@nestjs/websockets": "^9.4.0", "@sendgrid/mail": "^7.7.0", "@vendia/serverless-express": "^4.10.1", "aws-lambda": "^1.0.7", "aws-sdk": "^2.1362.0", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", "dotenv": "^16.0.3", "file-type": "^18.2.1", "jimp": "^0.22.7", "morgan": "^1.10.0", "nodemailer": "^6.9.1", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pg": "^8.10.0", "postmark": "^3.0.15", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "sharp": "^0.32.0", "socket.io": "^4.6.1", "stripe": "^12.2.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.4.0", "@nestjs/schematics": "^9.1.0", "@nestjs/testing": "^9.4.0", "@types/aws-lambda": "^8.10.114", "@types/express": "^4.17.17", "@types/jest": "^29.5.1", "@types/multer": "^1.4.7", "@types/node": "^18.15.12", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", "eslint": "^8.38.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "node-mocks-http": "^1.12.2", "prettier": "^2.8.7", "rimraf": "^5.0.0", "serverless-offline": "^12.0.4", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.0.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } }
tsconfig.json
{ "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, "esModuleInterop": true } }
serverless.yaml
service: SERVICE-NAME useDotenv: true plugins: - serverless-offline provider: name: aws runtime: nodejs12.x environment: POSTGRES_HOST: ${env:POSTGRES_HOST} PORT_OF_POSTGRES: ${env:PORT_OF_POSTGRES} POSTGRES_USER: ${env:POSTGRES_USER} POSTGRES_PASSWORD: ${env:POSTGRES_PASSWORD} POSTGRES_DATABASE: ${env:POSTGRES_DATABASE} JWT_SECRET: ${env:JWT_SECRET} TIMEZONE: ${env:TIMEZONE} ACCESS_KEY_ID: ${env:ACCESS_KEY_ID} SECRET_ACCESS_KEY: ${env:SECRET_ACCESS_KEY} BUCKET_NAME_FOR_SUMMARY: ${env:BUCKET_NAME_FOR_SUMMARY} SEND_GRID_KEY: ${env:SEND_GRID_KEY} STRIPE_SECRET_KEY: ${env:STRIPE_SECRET_KEY} STRIPE_PUBLIC_KEY: ${env:STRIPE_PUBLIC_KEY} functions: main: handler: dist/serverless.handler events: - http: method: ANY path: / - http: method: ANY path: '{proxy+'}
解决建议
调整SendGrid导入方式:将命名空间导入改为默认导入,适配无服务器环境的模块解析逻辑
修改sendgrid.service.ts中的导入代码:import SendGrid from '@sendgrid/mail';无服务器环境对ES模块和CommonJS模块的混合解析规则与本地Node环境存在差异,默认导入能避免模块导出结构异常。
升级AWS Lambda运行时版本:当前配置的
nodejs12.x已停止维护,对现代模块的支持不完善,将serverless.yaml中的运行时改为nodejs18.x(或更高LTS版本):provider: name: aws runtime: nodejs18.x延迟API密钥初始化:将API密钥的设置从构造函数移到
send方法中,避免冷启动时模块加载顺序问题:import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import SendGrid from '@sendgrid/mail'; @Injectable() export class SendgridService { constructor(private configService: ConfigService) {} async send(mail: typeof SendGrid.MailDataRequired) { SendGrid.setApiKey(this.configService.get('SEND_GRID_KEY')); return await SendGrid.send(mail); } }同时注入
ConfigService来获取环境变量,比直接读取process.env更符合NestJS的最佳实践。验证依赖兼容性:
- 部署前执行
npm install --production安装生产依赖,避免开发依赖干扰 - 将
@sendgrid/mail升级到最新稳定版:npm install @sendgrid/mail@latest,确保与新版Node环境兼容
- 部署前执行
内容的提问来源于stack exchange,提问作者Zeke John
相关产品推荐
相关产品推荐

