NestJS on Heroku always failing to bind port - heroku

I am trying to deploy my NestJS REST API on Heroku but I always get the following error:
Web process failed to bind to $PORT within 60 seconds of launch
My configuration is pretty straight forward:
In my main.ts I start my server with:
await app.listen(process.env.PORT || AppModule.port);
I added a Procfile in the root directory of my project which contains:
web: npm run start:prod
My package.json files contains these scripts:
"build": "tsc -p tsconfig.build.json",
"prestart:prod": "rimraf dist && npm run build",
"start:prod": "node dist/main.js",
The process on Heroku builds succesfully, prints out these seamingly reassuring lines:
TypeOrmModule dependencies initialized
SharedModule dependencies initialized
AppModule dependencies initialized
But then immediately crashes with:
Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
I use .env configuration across my application but I removed all HOST and PORT variables (and code references), so I have no clue what could be the cause of this error.
Am I missing something?
EDIT
I am hereby sharing my app.module and main.ts files:
app.module.ts
#Module({
imports: [
SharedModule,
TypeOrmModule.forRootAsync({
imports: [SharedModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
type: 'postgres',
host: configService.getString('POSTGRES_HOST'),
port: configService.getNumber('POSTGRES_DB_PORT'),
username: configService.getString('POSTGRES_USER'),
password: configService.getString('POSTGRES_PASSWORD'),
database: configService.getString('POSTGRES_DB'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
} as PostgresConnectionOptions),
}),
UserModule,
],
controllers: [
AppController,
],
providers: [
AppService,
],
})
export class AppModule {
static port: number;
static isDev: boolean;
constructor(configurationService: ConfigService) {
console.log(process.env.PORT);
AppModule.port = configurationService.getNumber('PORT');
AppModule.isDev = configurationService.getBoolean('ISDEV');
}
}
My configuration.service.ts is a simple utility that reads from .env files:
import * as dotenv from 'dotenv';
import * as path from 'path';
#Injectable()
export class ConfigService {
constructor() {
const filePath = path.resolve('.env');
dotenv.config({
path: filePath,
});
}
getNumber(key: string): number | undefined {
return +process.env[key] as number | undefined;
}
getBoolean(key: string): boolean {
return process.env[key] === 'true';
}
getString(key: string): string | undefined {
return process.env[key];
}
}
And finally my main.ts file:
async function bootstrap() {
console.log(process.env.PORT);
const app = await NestFactory.create(AppModule);
app.enableCors();
app.useGlobalPipes(new ValidationPipe(), new TimeStampPipe());
app.use(json({ limit: '5mb' }));
app.setGlobalPrefix('api/v1');
await app.listen(process.env.PORT || AppModule.port);
}
bootstrap();
Could it be that my configuration.service.ts is interfering with heroku's env file?

If you are using fastify instead express as your platform, you need to define the host to 0.0.0.0 explicitly like this :
const port = process.env.PORT || AppModule.port;
const host = '0.0.0.0';
await app.listen(port, host);
This problem is caused by the fastify library. See the related discussion here: Fastify with Heroku.

Just as a summary, be careful on the database connection timeout that could lead to a global timeout of the heroku bootstrap as describe above

Related

Default Laravel + Vite configuration throws WebSocket connection to failed:

So Laravel decided to innovate once again and fix what was not broken, so Mix is gone and now default asset bundling goes with Vite.
I'm following the absolute default in their documentation to a bunch of front-end bugs and finally only several remained:
I use Laragon with SSL.
I haven't configured anything additional and my vite.config.js looks like this:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '#vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});
When I run npm run dev and visit the Laragon domain I get the following in the console:
client.ts:78 WebSocket connection to 'wss://127.0.0.1:5173/' failed.
client.ts:48 [vite] failed to connect to websocket.
your current setup:
(browser) 127.0.0.1:5173/ <--[HTTP]--> 127.0.0.1:5173/ (server)
(browser) 127.0.0.1:5173/ <--[WebSocket (failing)]--> 127.0.0.1:5173/ (server)
Check out your Vite / network configuration and https://vitejs.dev/config/server-options.html#server-hmr .
I guess I need to configure my actual domain somewhere? I tried doing that in a server object in the config, but it didn't help those errors.
PS: Now in my vue files I need to import including the .vue extension e.g. import Button from '#/Components/Button.vue' is there any way I can ommit the .vue like it was with Laravel Mix?
I haven't use laragon before, but if you have a custom domain, eg, like
http://cutom-domain.test, you need to tell vite to use the certificate like so;
In your vite.config.js, add a server key with the following configuration
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '#vitejs/plugin-vue';
import fs from 'fs';
import { homedir } from 'os';
import { resolve } from 'path';
// Ignore the protocol on the host, ie do not put "http"
const host = 'cutom-domain.test';
const viteServerConfig = host => {
let keyPath = resolve(homedir(), `.config/valet/Certificates/${host}.key`)
let certificatePath = resolve(homedir(), `.config/valet/Certificates/${host}.crt`)
if (!fs.existsSync(keyPath)) {
return {}
}
if (!fs.existsSync(certificatePath)) {
return {}
}
return {
hmr: {host},
host,
https: {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certificatePath),
},
}
}
export default defineConfig({
server: viteServerConfig(host),
plugins: [
laravel({
input: 'resources/js/app.js',
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});
Credit to this blogpost that explains more - Making Vite and Valet play nice together
I don't know if it's still relevant, but looking in the source code of laravel-vite-plugin I found a way to solve this problem in a very simple way, without even changing the vite.config.js file.
Put these two variables in the .env file and set them with full path to .key and .crt files on your system:
VITE_DEV_SERVER_KEY='C:/laragon/etc/ssl/laragon.key'
VITE_DEV_SERVER_CERT='C:/laragon/etc/ssl/laragon.crt'
Do not change anything on vite.config.js file. Here is my (fresh install of laravel + jetstream w/ inertia and --ssr):
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '#vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
ssr: 'resources/js/ssr.js',
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});
And that's it. Run npm run dev and Vite will "magically" start a development server with https on.
VITE v4.0.4 ready in 1248 ms
➜ Local: https://laravel.test:5173/
➜ Network: https://192.168.1.2:5173/
➜ press h to show help
LARAVEL v9.48.0 plugin v0.7.3
➜ APP_URL: https://laravel.test/
Even though the configuration present in the official documentation also works, this way is much simpler, and the host, key and cert variables are not defined in the file, but they are dynamic reflecting the dev environment.
Hope this helps someone.
Here is the source where I found this, and you can also inspect in node_modules\laravel-vite-plugin\dist\index.js of your project.
When I do npm run build instead of regular npm run dev, the problem is gone. I guess, build mechanism is different for prod, so there is no WSS related errors in console.
So, in other words, perform a production Vite build and deploy it (if you are testing on a remote project).

NX component testing with cypress error process is not defined

I am using NX 14.5.1 and cypress 10.2.0. When I run cypress component testing for "libs/ui" always got "Process not defined" error. In the component call ".env" like this:
import consola from 'consola'
export const logger = consola.create({
level: process.env.NX_ENV_NAME === 'production' ? 0 : 5
})
This is my "cypress.config.ts":
import { defineConfig } from 'cypress';
import { nxComponentTestingPreset } from '#nrwl/react/plugins/component-testing';
export default defineConfig({
component: {
...nxComponentTestingPreset(__dirname)
}
})
And the error is like this:
process is not defined
ReferenceError
The following error originated from your test code, not from Cypress.
> process is not defined
When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure.
I think Cypress doesn't recognize my ".env". How do I pass my ".env" when I run component testing?
I think the basic problem is the app is server-side-rendered. The server compiles in Node where process is available, but the component test runs in the browser where process isn't valid.
This is the way you might tackle it:
In cypress.config.js load the process.env you need into Cypress.env
import { defineConfig } from 'cypress';
import { nxComponentTestingPreset } from '#nrwl/react/plugins/component-testing';
const setupNodeEvents = (on, config) => {
config.env = {
...config.env,
...process.env.NX_ENV_NAME,
}
return config
}
export default defineConfig({
component: {
...nxComponentTestingPreset(__dirname),
setupNodeEvents
}
})
In the component, check where the component is being run from.
import consola from 'consola'
const envName = window && window.Cypress
? window.Cypress.env('NX_ENV_NAME') // running in browser, take Cypress.env
: process.env.NX_ENV_NAME; // running SSR, take process.env
const level = envName === 'production' ? 0 : 5;
export const logger = consola.create({
level
})
This is messy but removes the restriction on running SSR code in a browser environment.

Heroku deploy chat bot

so, my code works both locally and docker image but, when I deploy to heroku it seems to work on first 1 minute and then app crashes, there is heroku logs, after crashing so what can be problems? any thought? thank you
there is my code
const { default: axios } = require('axios')
const telegramBot = require('node-telegram-bot-api')
const express = require('express')
const dotenv = require('dotenv').config()
const links = `
GitLab
Linkdin
Personal
`
const bot = new telegramBot(process.env.TOKEN, { polling: true })
const aboutText = 'Hello, I am learning NODE JS!'
const app = express()
bot.on('message', (message) => {
const id = message.chat.id
if (message.text === '/start' || message.text === '/help') {
bot.sendMessage(message.chat.id, 'avalable commands', {
reply_markup: {
keyboard: [['/about', '/links']],
resize_keyboard: true,
one_time_keyboard: true,
force_reply: true,
},
})
} else if (message.text === '/about') {
bot.sendMessage(id, aboutText)
} else if (message.text === '/links') {
bot.sendMessage(id, links, { parse_mode: 'HTML' })
} else {
bot.sendMessage(
message.chat.id,
'no such command! there are avalable commands',
{
reply_markup: {
keyboard: [['/about', '/links']],
resize_keyboard: true,
one_time_keyboard: true,
force_reply: true,
},
}
)
}
})
dockerfile:
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY ./app.js .
ENV port=8080
ENV TOKEN=21*****:AAEF******************fKiQ
EXPOSE 8080
CMD [ "node", "app.js" ]
Your app fails to bind to the port assigned by Heroku: you cannot set the port yourself (ie 8080) but instead bind to the port defined by the $PORT env variable.
See Why is my Node.js app crashing with an R10 error? to understand the details.
In your specific case if you run the Bot in polling mode (i.e. pulling the changes) you can instead use a worker node instead of a web: doing this you don't need to bind to the port as your application does not require to process incoming HTTP requests.

prisma.yml could not be found

I am trying to generate schema for my prisma data model while at the same time using secrets to restrict prisma access. After running prisma delete and prisma deploy, I run the command graphql get-schema -p prisma and get the following error message:
✖ prisma/prisma.yml could not be found.
Is there something wrong I am doing in my .graphqlconfig or how I am listing my prisma.yml? Thanks.
.graphqlconfig:
{
"projects": {
"prisma": {
"schemaPath": "generated/prisma.graphql",
"extensions": {
"prisma": "prisma/prisma.yml",
"endpoints": {
"default": "http://localhost:4466"
}
}
}
}
}
prisma/prisma.yml:
endpoint: http://localhost:4466
datamodel: datamodel.prisma
secret: 'secretFoo'
index.js:
import http from 'http';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import resolvers from './resolvers';
import schema from './generated/prisma.graphql';
import { Prisma } from 'prisma-binding';
const prisma = new Prisma({
endpoint: 'http://localhost:4466',
secret: 'secretFoo',
typeDefs: 'server/generated/prisma.graphql',
});
const server = new ApolloServer({
context: {
prisma,
},
resolvers,
typeDefs: schema,
});
const app = express();
server.applyMiddleware({ app });
const PORT = 5000;
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
});
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => server.stop());
}
You can generate a schema directly from your prisma.yml file, by adding the following to the file:
generate:
- generator: graphql-schema
output: ./generated/prisma.graphql
Then you can refer your .graphqlconfig to the generated file:
projects:
prisma:
schemaPath: generated/prisma.graphql
extensions:
endpoints:
dev: http://localhost:4466
You would generally restrict access to the management functionality of your endpoint through the Prisma docker-compose file (managementApiSecret in PRISMA_CONFIG). Then when you run commands like prisma deploy you would need to pass the appropriate environment variables through either the --env-file flag, or by having a dotenv file in the root of your application's directory (you also need the dotenv package installed in package.json.
Another way to secure your endpoint is to disable the GraphQL Playground altogether. I believe Apollo Server does this automatically when NODE_ENV is set to production, although you can do it explicitly with:
const server = new ApolloServer({
context: {
prisma,
},
resolvers,
typeDefs: schema,
playground: false, // <- Here
});
I'm sorry, I don't think this directly answered your question, but it may assist either way.

Does Mocha support multiple before hook for creating independent http server?

Here is my project structure:
src/
- demo-1/
- server.ts
- server.spec.ts
- demo-2/
- server.ts
- server.spec.ts
Each server.spec.ts has below setup:
import { start } from './server';
let server: http.Server;
before('start server', (done: Done) => {
server = start(done);
});
after('stop server', (done: Done) => {
server.close(done);
});
describe('test suites', () => {
//...
})
Here is my package.json scripts:
"scripts": {
"test": "NODE_ENV=test mocha --timeout=3000 --require=ts-node/register ./src/**/*.spec.ts"
},
When I run npm test, it gives me an error:
1) "before all" hook: start server:
Uncaught Error: listen EADDRINUSE :::4000
at Object._errnoException (util.js:1022:11)
at _exceptionWithHostPort (util.js:1044:20)
at Server.setupListenHandle [as _listen2] (net.js:1351:14)
at listenInCluster (net.js:1392:12)
at Server.listen (net.js:1476:7)
at Function.listen (node_modules/express/lib/application.js:618:24)
at Object.start (src/constructor-types/server.ts:33:14)
at Context.before (src/constructor-types/server.spec.ts:12:12)
at Server.app.listen (src/aliases/server.ts:42:7)
at emitListeningNT (net.js:1378:10)
at _combinedTickCallback (internal/process/next_tick.js:135:11)
at process._tickCallback (internal/process/next_tick.js:180:9)
2) "after all" hook: stop server:
Error: Not running
at Server.close (net.js:1604:12)
at emitCloseNT (net.js:1655:8)
at _combinedTickCallback (internal/process/next_tick.js:135:11)
at Immediate._tickCallback (internal/process/next_tick.js:180:9)
I expect each server.spec.ts works independently which means start the server and run its test suites one by one in order to avoiding http port conflict. Because these servers have a same http port.
Mocha run test files in parallel. Servers will be created same time into one port.
The workaround is assigning random port for each file (change start function to add dynamic port param)
import { start } from './server';
const PORT = Math.floor((Math.random() * 100000) + 1);
let server: http.Server;
before('start server', (done: Done) => {
// change start function accept port param
server = start(PORT, done);
});
after('stop server', (done: Done) => {
server.close(done);
});
Another solution is serial-mocha that support running test synchronous. However the package is old, and I don't know if it still work

Resources