Skip to content

Conversation

@ThyMinimalDev
Copy link
Contributor

@ThyMinimalDev ThyMinimalDev commented Jan 7, 2026

What does this PR do?

Adds Vercel serverless support for API v2 with a serverless entrypoint and a warm cache to reduce cold starts. Local development stays the same.

  • Fixes #XXXX (GitHub issue number)
  • Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

Changes

  • Serverless Handler: Adds a default export handler in main.ts that initializes the NestJS app and caches the instance across warm invocations
  • Vercel Configuration: Adds vercel.json with builds using @vercel/node and routes to handle all HTTP methods
  • Conditional Shutdown Hooks: Disables shutdown hooks when running on Vercel to align with lambda lifecycle
  • Preserved Imports: Keeps namespace imports (import * as X) for qs, cookie-parser, etc. to maintain compatibility

Updates since last revision

  • Switched vercel.json to use builds with @vercel/node pointing to src/main.ts and routes pattern (standard NestJS on Vercel approach)
  • Previous functions/rewrites configuration caused "pattern doesn't match any Serverless Functions inside the api directory" error

Visual Demo (For contributors especially)

N/A - Infrastructure/deployment change

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Deploy to Vercel and verify the API starts correctly
  2. Test API endpoints to confirm the serverless handler works
  3. Make multiple requests to verify warm cache is working (subsequent requests should be faster)
  4. Verify local development still works with yarn dev

Human Review Checklist

  • Verify @vercel/node can resolve workspace dependencies (@calcom/platform-*, @calcom/prisma, etc.)
  • Confirm path aliases (@/) resolve correctly when Vercel compiles TypeScript
  • Test actual deployment on Vercel to verify the configuration works
  • Confirm the serverless handler initializes and caches the NestJS app properly
  • Note: The handler uses any types for req/res - acceptable for serverless handler but worth noting

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

Link to Devin run: https://app.devin.ai/sessions/c453624347b247d5bfec8f091f657119
Requested by: morgan@cal.com (@ThyMinimalDev)

@ThyMinimalDev ThyMinimalDev requested a review from a team as a code owner January 7, 2026 15:23
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted.

Details:

No release type found in pull request title "Poc api v2 vercel". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit

@graphite-app graphite-app bot added foundation core area: core, team members only labels Jan 7, 2026
@vercel
Copy link

vercel bot commented Jan 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
api-v2 Ready Ready Preview, Comment Jan 8, 2026 7:37pm
3 Skipped Deployments
Project Deployment Review Updated (UTC)
cal Ignored Ignored Jan 8, 2026 7:37pm
cal-companion Ignored Ignored Preview Jan 8, 2026 7:37pm
cal-eu Ignored Ignored Jan 8, 2026 7:37pm

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/api/v2/src/main.ts">

<violation number="1" location="apps/api/v2/src/main.ts:21">
P1: Race condition: Multiple concurrent cold-start requests can initialize multiple Nest instances simultaneously. Use a promise-based singleton pattern to ensure only one initialization runs.</violation>
</file>

<file name="apps/api/v2/src/instrument.ts">

<violation number="1" location="apps/api/v2/src/instrument.ts:2">
P0: This import change will break Sentry initialization. The `@sentry/nestjs` package does not have a default export - it only exports named functions/integrations. Using `import Sentry from "@sentry/nestjs"` instead of `import * as Sentry from "@sentry/nestjs"` will cause `Sentry.init()` and `Sentry.prismaIntegration()` to fail at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -1,5 +1,5 @@
import { getEnv } from "@/env";
import * as Sentry from "@sentry/nestjs";
import Sentry from "@sentry/nestjs";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: This import change will break Sentry initialization. The @sentry/nestjs package does not have a default export - it only exports named functions/integrations. Using import Sentry from "@sentry/nestjs" instead of import * as Sentry from "@sentry/nestjs" will cause Sentry.init() and Sentry.prismaIntegration() to fail at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/v2/src/instrument.ts, line 2:

<comment>This import change will break Sentry initialization. The `@sentry/nestjs` package does not have a default export - it only exports named functions/integrations. Using `import Sentry from "@sentry/nestjs"` instead of `import * as Sentry from "@sentry/nestjs"` will cause `Sentry.init()` and `Sentry.prismaIntegration()` to fail at runtime.</comment>

<file context>
@@ -1,5 +1,5 @@
 import { getEnv } from "@/env";
-import * as Sentry from "@sentry/nestjs";
+import Sentry from "@sentry/nestjs";
 import { nodeProfilingIntegration } from "@sentry/profiling-node";
 
</file context>
Suggested change
import Sentry from "@sentry/nestjs";
import * as Sentry from "@sentry/nestjs";

✅ Addressed in 81d60ac

* CACHE: This allows the Nest app to persist across multiple
* serverless "warm" invocations, significantly reducing latency.
*/
let cachedServer: any;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Race condition: Multiple concurrent cold-start requests can initialize multiple Nest instances simultaneously. Use a promise-based singleton pattern to ensure only one initialization runs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/v2/src/main.ts, line 21:

<comment>Race condition: Multiple concurrent cold-start requests can initialize multiple Nest instances simultaneously. Use a promise-based singleton pattern to ensure only one initialization runs.</comment>

<file context>
@@ -5,42 +5,82 @@ import { NestFactory } from "@nestjs/core";
+ * CACHE: This allows the Nest app to persist across multiple
+ * serverless "warm" invocations, significantly reducing latency.
+ */
+let cachedServer: any;
+
+/**
</file context>
Fix with Cubic

- Revert namespace imports (import * as X) for qs, Sentry, jwt, tzdata
- Update vercel.json to use simpler configuration with buildCommand, outputDirectory, functions, and rewrites
- Remove complex two-step build with @vercel/static-build

Co-Authored-By: morgan@cal.com <morgan@cal.com>
@pull-request-size pull-request-size bot added size/M and removed size/L labels Jan 7, 2026
@devin-ai-integration devin-ai-integration bot changed the title Poc api v2 vercel feat: Add Vercel serverless deployment for API v2 Jan 7, 2026
Co-Authored-By: morgan@cal.com <morgan@cal.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/api/v2/vercel.json">

<violation number="1" location="apps/api/v2/vercel.json:6">
P1: Missing `buildCommand` will cause deployment failures. This NestJS monorepo app requires workspace dependencies to be built first (`yarn turbo run build --filter=@calcom/api-v2`). The `@vercel/node` builder alone cannot resolve workspace package dependencies like `@calcom/platform-libraries` or handle the complex build process. Consider restoring `buildCommand` or configuring Vercel root settings appropriately.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

"builds": [
{
"src": "src/main.ts",
"use": "@vercel/node"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing buildCommand will cause deployment failures. This NestJS monorepo app requires workspace dependencies to be built first (yarn turbo run build --filter=@calcom/api-v2). The @vercel/node builder alone cannot resolve workspace package dependencies like @calcom/platform-libraries or handle the complex build process. Consider restoring buildCommand or configuring Vercel root settings appropriately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/v2/vercel.json, line 6:

<comment>Missing `buildCommand` will cause deployment failures. This NestJS monorepo app requires workspace dependencies to be built first (`yarn turbo run build --filter=@calcom/api-v2`). The `@vercel/node` builder alone cannot resolve workspace package dependencies like `@calcom/platform-libraries` or handle the complex build process. Consider restoring `buildCommand` or configuring Vercel root settings appropriately.</comment>

<file context>
@@ -1,18 +1,16 @@
+  "builds": [
+    {
+      "src": "src/main.ts",
+      "use": "@vercel/node"
     }
-  },
</file context>

✅ Addressed in d972d86

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts">

<violation number="1" location="apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts:168">
P1: Rule violated: **Avoid Logging Sensitive Information**

This console.log statement can expose PII (email addresses) in production logs. The `query` object contains `email` and `teamMemberEmail` fields. Remove this debug log or use a proper logging framework with PII redaction.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@Req() req: ExpressRequest
): Promise<ApiResponse<{ slots: TimeSlots["slots"] | RangeSlots["slots"] }>> {
try {
console.log("Get available slots query", query);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: Avoid Logging Sensitive Information

This console.log statement can expose PII (email addresses) in production logs. The query object contains email and teamMemberEmail fields. Remove this debug log or use a proper logging framework with PII redaction.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts, line 168:

<comment>This console.log statement can expose PII (email addresses) in production logs. The `query` object contains `email` and `teamMemberEmail` fields. Remove this debug log or use a proper logging framework with PII redaction.</comment>

<file context>
@@ -165,6 +165,7 @@ export class SlotsController_2024_04_15 {
     @Req() req: ExpressRequest
   ): Promise<ApiResponse<{ slots: TimeSlots["slots"] | RangeSlots["slots"] }>> {
     try {
+      console.log("Get available slots query", query);
       const isTeamEvent =
         query.isTeamEvent === undefined
</file context>
Fix with Cubic

@ThyMinimalDev ThyMinimalDev requested a review from a team as a code owner January 8, 2026 18:51
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/platform/types/slots/slots-2024-04-15/inputs/index.ts">

<violation number="1" location="packages/platform/types/slots/slots-2024-04-15/inputs/index.ts:149">
P2: The filter `!isNaN(n)` does not filter out `null` values because `isNaN(null)` returns `false` (null coerces to 0). Consider using a more explicit check to handle null/undefined values.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


// 2. Map everything to a Number. class-validator @IsNumber
// needs the actual type to be 'number', not 'string'.
return array.map((val) => (typeof val === "string" ? parseInt(val, 10) : val)).filter((n) => !isNaN(n)); // Clean out any bad data
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The filter !isNaN(n) does not filter out null values because isNaN(null) returns false (null coerces to 0). Consider using a more explicit check to handle null/undefined values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/platform/types/slots/slots-2024-04-15/inputs/index.ts, line 149:

<comment>The filter `!isNaN(n)` does not filter out `null` values because `isNaN(null)` returns `false` (null coerces to 0). Consider using a more explicit check to handle null/undefined values.</comment>

<file context>
@@ -141,10 +141,12 @@ export class GetAvailableSlotsInput_2024_04_15 {
+
+    // 2. Map everything to a Number. class-validator @IsNumber
+    // needs the actual type to be 'number', not 'string'.
+    return array.map((val) => (typeof val === "string" ? parseInt(val, 10) : val)).filter((n) => !isNaN(n)); // Clean out any bad data
   })
   @IsArray()
</file context>
Suggested change
return array.map((val) => (typeof val === "string" ? parseInt(val, 10) : val)).filter((n) => !isNaN(n)); // Clean out any bad data
return array.map((val) => (typeof val === "string" ? parseInt(val, 10) : val)).filter((n) => n != null && !isNaN(n)); // Clean out any bad data

✅ Addressed in 8e5c2ca

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/platform/types/slots/slots-2024-04-15/inputs/index.ts">

<violation number="1">
P2: Missing NaN filter: Invalid values like `"abc"` will result in `NaN` being included in the array, whereas the original code filtered these out with `.filter((n) => !isNaN(n))`.</violation>

<violation number="2">
P1: Breaking change: Single values are no longer wrapped in arrays. Query params like `?routedTeamMemberIds=5` (common for single-element queries) will now fail the `@IsArray()` validation since the string `"5"` is returned as-is instead of being converted to `[5]`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only foundation size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants