Skip to content

feat(deploy): #66 apps/api/vercel.json for second Vercel project#72

Closed
fray-cloud wants to merge 6 commits into
devfrom
deploy/66-api-vercel-json
Closed

feat(deploy): #66 apps/api/vercel.json for second Vercel project#72
fray-cloud wants to merge 6 commits into
devfrom
deploy/66-api-vercel-json

Conversation

@fray-cloud

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

Copy link
Copy Markdown
Owner

Summary

Stacked on #71. Vercel config for the api project (separate from web). When the user creates a second Vercel project pointing at this repo with Root Directory set to apps/api, this file becomes its config.

apps/api/vercel.json (new)

apps/api/tsconfig.json

Adds experimentalDecorators, emitDecoratorMetadata, target: es2021, module: commonjs at the project tsconfig level. Vercel's @vercel/node compiler reads the nearest tsconfig.json (not tsconfig.app.json), so without these flags the NestJS decorators in api/index.ts → src/app/app.module would lose their metadata and DI would fail at cold start. The flags duplicate what tsconfig.app.json already sets, so nx build api is unaffected.

The actual project creation in the Vercel Dashboard + env var configuration is documented in #67 (deployment matrix).

Closes #66.

Test plan

  • npx nx affected -t lint test build --exclude web-e2e green
  • nx build api (webpack bundler for local) still produces dist/apps/api/main.js

🤖 Generated with Claude Code

Summary by Sourcery

Configure the API app for Vercel deployment and route web traffic through a secure proxy to the public data portal v2 API.

New Features:

  • Add a Next.js route handler that proxies public-data-portal requests, injecting the server-only DATA_GO_KR_SERVICE_KEY so the browser never sees it.
  • Expose a Vercel-compatible serverless entrypoint for the NestJS API using a cached serverless Express handler.
  • Support multiple image URLs from the v2 abandonmentPublic API in shared types and UI by introducing popfile1..popfile8 fields.

Enhancements:

  • Update web API calls to use the v2 public-data-portal endpoints and the new proxy base path instead of calling the upstream service directly from the browser.
  • Align the API app TypeScript configuration with NestJS requirements so Vercel’s Node runtime can compile decorators correctly.
  • Adjust dependencies to support the serverless Express integration and remove unused Vercel Nx tooling.

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
Vercel config for the api project (separate from web). When the user
creates a second Vercel project pointing at this repo with
Root Directory set to apps/api, this file becomes its config.

apps/api/vercel.json:
- framework: null — we ship a Node Function, not a framework preset
- functions["api/index.ts"]: @vercel/node@5 runtime, maxDuration 30s
- rewrites: /api and /api/* both resolve to /api/index so the
  serverless-express handler from #65 receives the full request

apps/api/tsconfig.json:
- experimentalDecorators + emitDecoratorMetadata + target es2021 +
  module commonjs added at the project tsconfig level. Vercel's
  @vercel/node compiler reads the nearest tsconfig.json (not
  tsconfig.app.json), so without these flags the NestJS decorators
  in api/index.ts → src/app/app.module would lose their metadata
  and DI would fail at cold start. The flags duplicate what
  tsconfig.app.json already sets, so nx build api is unaffected.

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

The actual project creation in the Vercel Dashboard + env var
configuration is documented in #67 (deployment matrix).

Refs #66
@sourcery-ai

sourcery-ai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Configures a separate Vercel deployment target for the NestJS API app using a new serverless entrypoint and per-project vercel.json, while routing web app traffic through a new Next.js route handler that proxies to the public-data-portal v2 APIs and updates shared types and call sites accordingly.

Sequence diagram for Next.js data-go-kr proxy route handler

sequenceDiagram
    actor User
    participant Browser
    participant WebApp
    participant RouteHandler
    participant DataGoKrAPI

    User->>Browser: Initiate search in UI
    Browser->>WebApp: Request /api/data-go-kr/{path}?query
    WebApp->>RouteHandler: Invoke GET handler
    RouteHandler->>RouteHandler: Read DATA_GO_KR_SERVICE_KEY from env
    RouteHandler->>RouteHandler: Build upstream URL with serviceKey and _type=json
    RouteHandler->>DataGoKrAPI: GET abandonmentPublicService_v2/{path}
    DataGoKrAPI-->>RouteHandler: JSON response
    RouteHandler-->>WebApp: Forward body and content-type
    WebApp-->>Browser: Return response to client
    Browser-->>User: Render search results without exposing API key
Loading

Sequence diagram for Vercel Node Function NestJS bootstrap and reuse

sequenceDiagram
    participant Client
    participant Vercel
    participant ApiFunction
    participant NestFactory
    participant ExpressApp

    Client->>Vercel: HTTP request /api
    Vercel->>ApiFunction: Invoke default handler(event, context, callback)
    alt First invocation (cold start)
        ApiFunction->>ApiFunction: cachedHandler is undefined
        ApiFunction->>ExpressApp: Create express application instance
        ApiFunction->>NestFactory: create(AppModule, ExpressAdapter(expressApp))
        NestFactory-->>ApiFunction: Nest application instance
        ApiFunction->>ApiFunction: app.setGlobalPrefix(api)
        ApiFunction->>ApiFunction: app.init()
        ApiFunction->>ApiFunction: cachedHandler = serverlessExpress(app: expressApp)
        ApiFunction->>ApiFunction: Invoke cachedHandler(event, context, callback)
    else Subsequent invocation (warm)
        ApiFunction->>ApiFunction: Reuse cachedHandler
        ApiFunction->>ApiFunction: Invoke cachedHandler(event, context, callback)
    end
    ApiFunction-->>Vercel: Lambda style response
    Vercel-->>Client: HTTP response
Loading

Class diagram for Vercel Node Function NestJS integration

classDiagram
    class ApiIndex {
      - cachedHandler Handler
      + bootstrap() Promise_Handler_
      + handler(event any, context any, callback any) Promise_any_
    }

    class AppModule

    class NestFactory {
      + create(moduleRef any, adapter ExpressAdapter) Promise_NestApplication_
    }

    class ExpressAdapter {
      + ExpressAdapter(expressApp ExpressApplication)
    }

    class ExpressApplication

    class ServerlessExpress {
      + serverlessExpress(app ExpressApplication) Handler
    }

    class Handler

    class NestApplication {
      + setGlobalPrefix(prefix string) void
      + init() Promise_void_
    }

    ApiIndex ..> AppModule : uses
    ApiIndex ..> NestFactory : uses
    ApiIndex ..> ExpressAdapter : uses
    ApiIndex ..> ServerlessExpress : uses
    NestFactory ..> NestApplication : creates
    ExpressAdapter ..> ExpressApplication : wraps
    ServerlessExpress ..> Handler : returns
    ApiIndex o-- Handler : holds cachedHandler
    NestApplication ..> ExpressApplication : runs_on
Loading

Class diagram for updated APIResponse item image fields

classDiagram
    class APIResponse~T~ {
      + response Header
      + body Body~T~
    }

    class Body~T~ {
      + items T[]
      + numOfRows number
      + pageNo number
      + totalCount number
    }

    class AnimalItem {
      + 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 string
      + specialMark string
      + careNm string
      + careTel string
      + careAddr string
      + orgNm string
      + chargeNm string
      + officetel string
    }

    APIResponse ..> Body : contains
    Body ..> AnimalItem : items_of
Loading

File-Level Changes

Change Details Files
Introduce a Vercel-specific serverless entrypoint and configuration for the NestJS API app so it can run as its own Vercel project rooted at apps/api.
  • Add apps/api/api/index.ts as a Node Function entrypoint that boots NestJS on an Express adapter, sets a global /api prefix, wraps it with @codegenie/serverless-express, and caches the handler across cold starts.
  • Create apps/api/vercel.json (and remove the root vercel.json) to declare framework: null, configure the api/index.ts function to use @vercel/node@5 with a 30s timeout, and add rewrites mapping /api and /api/* to /api/index so all traffic hits the serverless-express handler.
  • Adjust apps/api/tsconfig.json compilerOptions (experimentalDecorators, emitDecoratorMetadata, target es2021, module commonjs) so the nearest tsconfig matches Nest requirements when compiled by @vercel/node.
apps/api/api/index.ts
apps/api/vercel.json
vercel.json
apps/api/tsconfig.json
Move public-data-portal calls behind a Next.js Route Handler proxy that injects a server-only API key and switches clients to the v2 endpoints.
  • Add apps/web/app/api/data-go-kr/[...path]/route.ts which proxies GET requests to the abandonmentPublicService_v2 upstream, injects DATA_GO_KR_SERVICE_KEY and _type=json into query params, and returns upstream status/body while handling missing key and fetch errors.
  • Update apps/web/src/new-api/service.ts to call the new /api/data-go-kr route instead of hitting the public-data-portal directly, removing the exposed NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY and _type=json from client-side requests.
  • Adjust new-api call sites (animalInfo, kind, shelter, sido, sigungu) to use the *_v2 variants of the upstream paths, matching the new v2 backend API.
  • Expand the shared APIResponse type to support multiple image fields popfile1..popfile8 and update AnimalCard to use popfile1, aligning the UI with the v2 response shape.
apps/web/app/api/data-go-kr/[...path]/route.ts
apps/web/src/new-api/service.ts
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 root package dependencies to support the new serverless NestJS entrypoint and drop unused tooling.
  • Add @codegenie/serverless-express and @types/aws-lambda dependencies used by the new apps/api/api/index.ts handler.
  • Remove @vercel/remote-nx dev dependency, which is no longer needed with the new deployment setup.
  • Update package-lock.json accordingly and keep auxiliary config files (e.g., apps/api/tsconfig.app.json, apps/web/.env.example) in sync with the new behavior.
package.json
package-lock.json
apps/api/tsconfig.app.json
apps/web/.env.example

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:39pm

@nx-cloud

nx-cloud Bot commented Apr 13, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit f7f2a47

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

☁️ Nx Cloud last updated this comment at 2026-04-13 13:39:38 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 Next route handler (apps/web/app/api/data-go-kr/[...path]/route.ts), context.params shouldn’t be typed or treated as a Promise—use context: { params: { path: string[] } } and drop the await to align with Next’s route handler typings and avoid confusion.
  • The data-go-kr proxy currently buffers the full upstream response with await upstream.text(); if responses can get large, consider returning a streamed response or upstream.body directly via NextResponse to reduce memory usage and latency.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the Next route handler (`apps/web/app/api/data-go-kr/[...path]/route.ts`), `context.params` shouldn’t be typed or treated as a `Promise`—use `context: { params: { path: string[] } }` and drop the `await` to align with Next’s route handler typings and avoid confusion.
- The data-go-kr proxy currently buffers the full upstream response with `await upstream.text()`; if responses can get large, consider returning a streamed response or `upstream.body` directly via `NextResponse` to reduce memory usage and latency.

## Individual Comments

### Comment 1
<location path="apps/web/src/new-site/search/card/AnimalCard.tsx" line_range="25-27" />
<code_context>
       </div>
       <figure className="px-10 pt-10">
-        <img src={item.popfile} alt="pet" className="rounded-xl h-32" />
+        <img src={item.popfile1} alt="pet" className="rounded-xl h-32" />
       </figure>
       <CardContent className="flex flex-col items-center text-center">
</code_context>
<issue_to_address>
**suggestion:** Handle missing or empty `popfile1` more defensively in the card rendering.

Since the API can now return up to 8 image fields, `popfile1` may be empty while other `popfileX` values are present. Consider either falling back to the first non-empty `popfileX` or only rendering the `<img>` when `popfile1` is truthy and otherwise showing a placeholder, to avoid broken images with partial data.

```suggestion
      {(() => {
        const imageSrc =
          item.popfile1 ||
          item.popfile2 ||
          item.popfile3 ||
          item.popfile4 ||
          item.popfile5 ||
          item.popfile6 ||
          item.popfile7 ||
          item.popfile8

        return imageSrc ? (
          <figure className="px-10 pt-10">
            <img src={imageSrc} alt="pet" className="rounded-xl h-32" />
          </figure>
        ) : (
          <figure className="px-10 pt-10">
            <div className="rounded-xl h-32 w-full flex items-center justify-center bg-gray-100 text-gray-400">
              No image available
            </div>
          </figure>
        )
      })()}
```
</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 25 to 27
<figure className="px-10 pt-10">
<img src={item.popfile} alt="pet" className="rounded-xl h-32" />
<img src={item.popfile1} alt="pet" className="rounded-xl h-32" />
</figure>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Handle missing or empty popfile1 more defensively in the card rendering.

Since the API can now return up to 8 image fields, popfile1 may be empty while other popfileX values are present. Consider either falling back to the first non-empty popfileX or only rendering the <img> when popfile1 is truthy and otherwise showing a placeholder, to avoid broken images with partial data.

Suggested change
<figure className="px-10 pt-10">
<img src={item.popfile} alt="pet" className="rounded-xl h-32" />
<img src={item.popfile1} alt="pet" className="rounded-xl h-32" />
</figure>
{(() => {
const imageSrc =
item.popfile1 ||
item.popfile2 ||
item.popfile3 ||
item.popfile4 ||
item.popfile5 ||
item.popfile6 ||
item.popfile7 ||
item.popfile8
return imageSrc ? (
<figure className="px-10 pt-10">
<img src={imageSrc} alt="pet" className="rounded-xl h-32" />
</figure>
) : (
<figure className="px-10 pt-10">
<div className="rounded-xl h-32 w-full flex items-center justify-center bg-gray-100 text-gray-400">
No image available
</div>
</figure>
)
})()}

@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/66-api-vercel-json 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