Skip to content

feat(deploy): #65 NestJS serverless handler entrypoint#71

Closed
fray-cloud wants to merge 5 commits into
devfrom
deploy/65-nest-serverless
Closed

feat(deploy): #65 NestJS serverless handler entrypoint#71
fray-cloud wants to merge 5 commits into
devfrom
deploy/65-nest-serverless

Conversation

@fray-cloud

@fray-cloud fray-cloud commented Apr 13, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked on #70. Adds apps/api/api/index.ts as the Vercel Node Function entrypoint for the NestJS app. Local dev still uses apps/api/src/main.ts via nx serve api; this handler is only consumed by Vercel.

Pattern (per NestJS FAQ /faq/serverless.md)

  • Wrap NestFactory.create with ExpressAdapter so we can hand the underlying express app to @codegenie/serverless-express
  • Cache the resulting handler in a module-scope var so cold start pays bootstrap cost once and warm invocations skip it
  • export default Handler from aws-lambda types so Vercel's Node runtime auto-detects the entry

Why @codegenie/ instead of @vendia/ (roadmap)

@vendia/serverless-express was renamed to @codegenie/serverless-express. The Nest docs already use the new name.

Other changes

  • apps/api/tsconfig.app.json include adds api/**/*.ts so the new entrypoint type-checks under nx lint api / nx build api
  • src/main.ts is untouched
  • webpack.config.js still bundles main.ts via @nx/webpack/app-plugin, so nx build api and nx serve api keep working identically

The Vercel project + per-app vercel.json wiring lands in #66.

Closes #65.

Test plan

  • npx nx affected -t lint test build --exclude web-e2e green
  • New api/index.ts lints clean

🤖 Generated with Claude Code

Summary by Sourcery

Introduce a serverless NestJS handler entrypoint for Vercel and migrate the web app to use a server-side proxy and v2 endpoints for the public data API.

New Features:

  • Add a Vercel Node Function entrypoint for the NestJS API using a serverless Express adapter and AWS Lambda-style handler.
  • Expose a Next.js route handler that proxies public-data-portal requests and injects the server-only DATA_GO_KR_SERVICE_KEY.

Enhancements:

  • Update web API client calls to target the new Next.js proxy route and the v2 versions of the external abandonment/kind/shelter/sido/sigungu endpoints.
  • Extend shared response types and UI components to support multiple image fields from the v2 public data API.
  • Adjust API TypeScript configuration and project dependencies to support the new serverless handler and proxy setup while removing unused Vercel NX tooling.

Build:

  • Add @codegenie/serverless-express and @types/aws-lambda dependencies and remove @vercel/remote-nx from the project.

Root vercel.json was a Vite SPA rewrite (\`/(.*) → /index.html\`) that
would break Next.js deploys by routing every path into a nonexistent
index.html. The Next App Router handles routing itself — no rewrite
needed at the Vercel layer.

Per the Vercel monorepo guide and the roadmap, web and api will each
become their own Vercel project with Root Directory apps/web and
apps/api respectively. Root-level vercel.json would apply to both and
cannot express per-app config coherently, so it stays removed.

- nx affected -t lint test build --exclude web-e2e green
- nx.json sharedGlobals previously referenced .circleci and
  .github/workflows/ci.yml, not vercel.json — no cache invalidation
  to worry about

Refs #62
Closes the server-only half of #3 by routing every public-data-portal
call through a Next Route Handler that reads the key from
process.env.DATA_GO_KR_SERVICE_KEY (no NEXT_PUBLIC_ prefix). The
browser bundle no longer carries the key.

apps/web/app/api/data-go-kr/[...path]/route.ts (new):
- Catch-all GET handler that mirrors the upstream
  http://apis.data.go.kr/1543061/abandonmentPublicSrvc/<path> structure
- Reads search params from the incoming request, injects serviceKey
  and _type=json server-side, then forwards via fetch
- Passes through status/content-type, returns 500 if the env var is
  missing, 502 if the upstream fetch throws

apps/web/src/new-api/service.ts:
- baseURL switched to '/api/data-go-kr' (relative path, no host)
- Drops the serviceKey query param entirely — the proxy handles it
- Drops the import.meta / process.env env-check code
- Same export surface (getAPI, serviceAPI) so all 5 callers under
  new-api/{animalInfo,kind,sigungu,shelter,sido} keep working
  unchanged

apps/web/.env.example:
- NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY → DATA_GO_KR_SERVICE_KEY
- Comment updated: server-only, read by the Route Handler

Build output:
  ○ /
  ○ /_not-found
  ƒ /api/data-go-kr/[...path]   ← new dynamic route
  ○ /like
  ○ /search

- nx affected -t lint test build --exclude web-e2e green

Refs #63
The public-data-portal endpoint moved to v2 with a renamed base path
and per-resource _v2 suffixes. Update the proxy and the consumers in
the same PR so the Route Handler delivered in the previous commit
keeps working end-to-end.

Route Handler upstream URL:
- http://apis.data.go.kr/1543061/abandonmentPublicSrvc
+ https://apis.data.go.kr/1543061/abandonmentPublicService_v2
(now also HTTPS, matching the v2 spec at data.go.kr/data/15098931)

new-api path constants (all five callers):
- /abandonmentPublic → /abandonmentPublic_v2
- /sido → /sido_v2
- /sigungu → /sigungu_v2
- /kind → /kind_v2
- /shelter → /shelter_v2

shared-types AnimalInfo:
- popfile (single Image URL) → popfile1 (required) plus optional
  popfile2..popfile8. The v2 schema returns up to 8 image URLs per
  rescue notice; we only render the first today, but the optional
  fields are typed for the gallery work in #18 e2e / future ports.

apps/web AnimalCard.tsx:
- item.popfile → item.popfile1

- nx affected -t lint test build --exclude web-e2e green

Refs #63
@vercel/remote-nx wires Nx Remote Cache into a Vercel-hosted endpoint
via tasksRunnerOptions in nx.json. Our nx.json has never had any
tasksRunnerOptions wired to it (we now use the standard Nx Cloud
runner via nxCloudAccessToken), so the dependency was dead weight.

- grep '@vercel/remote-nx' apps libs → zero source references
- nx affected -t lint test build --exclude web-e2e green (10/10 cache)

Refs #64
Adds apps/api/api/index.ts as the Vercel Node Function entrypoint
for the NestJS app. Local dev keeps using apps/api/src/main.ts via
`nx serve api`; this handler is only consumed by Vercel.

Pattern (per NestJS FAQ /faq/serverless.md):
- Wrap NestFactory.create with ExpressAdapter so we can hand the
  underlying express app to @codegenie/serverless-express
- Cache the resulting handler in a module-scope var so cold start
  pays the bootstrap cost once and warm invocations skip it
- Export default Handler from aws-lambda types so Vercel's Node
  runtime auto-detects the entry

Why @codegenie/ instead of @vendia/ as the roadmap mentions:
@vendia/serverless-express was renamed to @codegenie/serverless-
express; the Nest docs already use the new name.

apps/api/tsconfig.app.json:
- include adds api/**/*.ts so the new entrypoint type-checks under
  nx lint api / nx build api

src/main.ts is untouched. webpack.config.js still bundles main.ts
via @nx/webpack/app-plugin, so `nx build api` and `nx serve api`
keep working identically.

- nx affected -t lint test build --exclude web-e2e green
- new file lints clean

The Vercel project + vercel.json wiring lands in #66.

Refs #65
@sourcery-ai

sourcery-ai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a serverless-friendly NestJS entrypoint for the API app to run on Vercel’s Node runtime, while routing the web app’s public-data-portal traffic through a Next.js route handler that injects a server-only API key and migrates consumers to the v2 upstream API and response shape.

Sequence diagram for browser calls through Next route handler to public data API v2

sequenceDiagram
  actor User
  participant Browser
  participant NextRouteHandler as Next_route_handler_app_api_data_go_kr
  participant PublicAPI as Public_data_portal_v2

  User->>Browser: Perform search in new site
  Browser->>NextRouteHandler: GET /api/data-go-kr/abandonmentPublic_v2?params
  NextRouteHandler->>NextRouteHandler: Read DATA_GO_KR_SERVICE_KEY from env
  NextRouteHandler->>PublicAPI: GET /abandonmentPublicService_v2/abandonmentPublic_v2
  Note over NextRouteHandler,PublicAPI: Adds serviceKey and _type=json query params
  PublicAPI-->>NextRouteHandler: JSON response body
  NextRouteHandler-->>Browser: JSON response proxy
  Browser-->>User: Render results and animal images using popfile1
Loading

Sequence diagram for Vercel Node runtime invoking NestJS serverless handler

sequenceDiagram
  participant VercelNode as Vercel_Node_runtime
  participant NestHandler as Nest_serverless_handler_index_ts
  participant NestApp as Nest_application

  VercelNode->>NestHandler: Invoke default Handler(event, context)
  alt First invocation (cold start)
    NestHandler->>NestHandler: cachedHandler is undefined
    NestHandler->>NestApp: bootstrap()
    NestApp->>NestApp: NestFactory.create(AppModule, ExpressAdapter)
    NestApp->>NestApp: app.setGlobalPrefix(api)
    NestApp->>NestApp: app.init()
    NestApp-->>NestHandler: express app instance
    NestHandler->>NestHandler: Wrap with serverlessExpress
    NestHandler->>NestHandler: Cache resulting Handler
    NestHandler-->>VercelNode: Invoke cachedHandler(event, context)
  else Subsequent invocations (warm)
    NestHandler->>NestHandler: Use cachedHandler
    NestHandler-->>VercelNode: Invoke cachedHandler(event, context)
  end
Loading

Class diagram for updated APIResponse image fields and AnimalCard usage

classDiagram
  class APIResponseItem {
    +string desertionNo
    +string filename
    +string happenDt
    +string happenPlace
    +string kindCd
    +string colorCd
    +string age
    +string weight
    +string noticeNo
    +string noticeSdt
    +string noticeEdt
    +string popfile1
    +string popfile2
    +string popfile3
    +string popfile4
    +string popfile5
    +string popfile6
    +string popfile7
    +string popfile8
    +string processState
    +string sexCd
    +string neuterYn
  }

  class APIResponse~T~ {
    +T responseBody
  }

  class AnimalCardProps {
    +APIResponseItem item
  }

  class AnimalCard {
    +AnimalCardProps props
    +render()
  }

  AnimalCardProps o-- APIResponseItem
  AnimalCard o-- AnimalCardProps

  note for APIResponseItem "popfile replaced by popfile1..popfile8 for v2 image fields"
  note for AnimalCard "Uses item.popfile1 as image source"
Loading

File-Level Changes

Change Details Files
Add a Vercel-compatible serverless handler entrypoint for the NestJS API that wraps the Express app with @codegenie/serverless-express and caches the handler across invocations.
  • Create apps/api/api/index.ts as the Vercel Node Function entrypoint using NestFactory with ExpressAdapter and express instance.
  • Set a global API prefix on the Nest app and initialize it before passing the underlying express app to serverlessExpress.
  • Implement module-scope handler caching so cold starts pay Nest bootstrap cost once while warm invocations reuse the existing handler.
  • Export a default aws-lambda Handler so Vercel’s Node runtime can auto-detect the function entrypoint.
apps/api/api/index.ts
Update API client on the Next.js web app to call a new internal Next Route Handler proxy instead of the external public-data-portal endpoint and remove client-side exposure of the service key.
  • Change axios baseURL from the public-data-portal URL to the Next internal route /api/data-go-kr in the shared service client.
  • Remove use of NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY and related console warning from the browser-side client, so the key is no longer sent from the client or committed to git history.
  • Add a Next.js app router route handler that proxies /api/data-go-kr/[...path] to the upstream v2 API, injecting the DATA_GO_KR_SERVICE_KEY and JSON response type on the server side and streaming through status and content-type.
apps/web/src/new-api/service.ts
apps/web/app/api/data-go-kr/[...path]/route.ts
Migrate web API consumers and shared types to the public-data-portal v2 endpoints and multi-image response format, and update UI usage accordingly.
  • Change all web-side request helpers to call the *_v2 variants of the upstream endpoints (abandonmentPublic_v2, kind_v2, shelter_v2, sido_v2, sigungu_v2).
  • Adjust the shared APIResponse type to model multiple image fields (popfile1..popfile8) instead of a single popfile string, matching the v2 API.
  • Update the AnimalCard component to use popfile1 for the primary image when rendering search results.
apps/web/src/new-api/animalInfo/index.ts
apps/web/src/new-api/kind/index.ts
apps/web/src/new-api/shelter/index.ts
apps/web/src/new-api/sido/index.ts
apps/web/src/new-api/sigungu/index.ts
libs/shared-types/src/lib/responseAPI.ts
apps/web/src/new-site/search/card/AnimalCard.tsx
Adjust project configuration and dependencies to support the new serverless handler and clean up unused tooling.
  • Add @codegenie/serverless-express as a runtime dependency and @types/aws-lambda as a dev dependency for the serverless handler type definitions.
  • Update apps/api/tsconfig.app.json includes to cover api/**/*.ts so the new entrypoint is type-checked and built by Nx.
  • Remove @vercel/remote-nx from devDependencies now that it is no longer used, and delete the root vercel.json in favor of per-app configuration handled in a separate PR.
package.json
package-lock.json
apps/api/tsconfig.app.json
vercel.json

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented Apr 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
animal-project Error Error Apr 13, 2026 1:37pm

@nx-cloud

nx-cloud Bot commented Apr 13, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 777191f

Command Status Duration Result
nx build web ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-13 13:37:33 UTC

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In the apps/web/app/api/data-go-kr/[...path]/route.ts handler, context.params is declared as a Promise, but Next App Router passes this synchronously; you can drop the Promise and await to match the actual runtime shape and avoid unnecessary async wrapping.
  • The new Nest serverless entrypoint (apps/api/api/index.ts) bootstraps the app with only setGlobalPrefix('api'); consider mirroring any additional configuration in src/main.ts (e.g., CORS, global pipes/filters/interceptors, versioning) so behavior is consistent between local nx serve and Vercel.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `apps/web/app/api/data-go-kr/[...path]/route.ts` handler, `context.params` is declared as a `Promise`, but Next App Router passes this synchronously; you can drop the `Promise` and `await` to match the actual runtime shape and avoid unnecessary async wrapping.
- The new Nest serverless entrypoint (`apps/api/api/index.ts`) bootstraps the app with only `setGlobalPrefix('api')`; consider mirroring any additional configuration in `src/main.ts` (e.g., CORS, global pipes/filters/interceptors, versioning) so behavior is consistent between local `nx serve` and Vercel.

## Individual Comments

### Comment 1
<location path="apps/web/app/api/data-go-kr/[...path]/route.ts" line_range="6-8" />
<code_context>
+const UPSTREAM_BASE =
+  'https://apis.data.go.kr/1543061/abandonmentPublicService_v2';
+
+export async function GET(
+  request: NextRequest,
+  context: { params: Promise<{ path: string[] }> }
+) {
+  const serviceKey = process.env.DATA_GO_KR_SERVICE_KEY;
</code_context>
<issue_to_address>
**issue:** The `context.params` type should not be a `Promise`; using `Promise<{ path: string[] }>` is misleading and unnecessary.

In Next.js route handlers, `context.params` is a plain object, not a promise. Typing it as `Promise<{ path: string[] }>` diverges from the framework’s types, forces unnecessary `await`s, and may break when Next’s typings change.

Use a synchronous params type instead:

```ts
export async function GET(
  request: NextRequest,
  context: { params: { path: string[] } }
) {
  const { path } = context.params;
  // ...
}
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +6 to +8
export async function GET(
request: NextRequest,
context: { params: Promise<{ path: string[] }> }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: The context.params type should not be a Promise; using Promise<{ path: string[] }> is misleading and unnecessary.

In Next.js route handlers, context.params is a plain object, not a promise. Typing it as Promise<{ path: string[] }> diverges from the framework’s types, forces unnecessary awaits, and may break when Next’s typings change.

Use a synchronous params type instead:

export async function GET(
  request: NextRequest,
  context: { params: { path: string[] } }
) {
  const { path } = context.params;
  // ...
}

@fray-cloud

Copy link
Copy Markdown
Owner Author

Superseded by #73 — full #62#67 chain lands via #73.

@fray-cloud fray-cloud closed this Apr 13, 2026
@fray-cloud fray-cloud deleted the deploy/65-nest-serverless branch April 13, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant