Wire frontend into Docker Compose with type-safe API client
- Add frontend service to docker-compose.yml (port 3000, BACKEND_URL env var) - Add frontend/Dockerfile using adapter-node for plain Node/Docker runtime - Switch svelte.config.js from adapter-auto to adapter-node - Generate OpenAPI types from backend spec (openapi-typescript + openapi-fetch) - Add src/lib/server/api.ts as server-only typed API client factory - Add generate:api script to regenerate types when backend spec changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,5 +32,17 @@ services:
|
|||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: mealprep-frontend
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: http://app:8080
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
15
frontend/Dockerfile
Normal file
15
frontend/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM node:22-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/build build/
|
||||||
|
COPY --from=build /app/node_modules node_modules/
|
||||||
|
COPY package.json .
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
CMD ["node", "build"]
|
||||||
4031
frontend/package-lock.json
generated
Normal file
4031
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
frontend/package.json
Normal file
41
frontend/package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:ui": "vitest --ui",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"generate:api": "curl -s http://localhost:8080/v3/api-docs -o src/lib/api/openapi.json && openapi-typescript src/lib/api/openapi.json -o src/lib/api/schema.d.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.59.1",
|
||||||
|
"@sveltejs/adapter-auto": "^7.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.5.4",
|
||||||
|
"@sveltejs/kit": "^2.50.2",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/svelte": "^5.3.1",
|
||||||
|
"@vitest/ui": "^4.1.2",
|
||||||
|
"jsdom": "^29.0.1",
|
||||||
|
"openapi-typescript": "^7.13.0",
|
||||||
|
"svelte": "^5.54.0",
|
||||||
|
"svelte-check": "^4.4.2",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vitest": "^4.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"openapi-fetch": "^0.17.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/src/lib/api/openapi.json
Normal file
1
frontend/src/lib/api/openapi.json
Normal file
File diff suppressed because one or more lines are too long
1992
frontend/src/lib/api/schema.d.ts
vendored
Normal file
1992
frontend/src/lib/api/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
frontend/src/lib/server/api.ts
Normal file
19
frontend/src/lib/server/api.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import createClient from 'openapi-fetch';
|
||||||
|
import type { paths } from '$lib/api/schema.d.ts';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
|
// Usage in +page.server.ts load functions and form actions:
|
||||||
|
//
|
||||||
|
// export const load = async ({ fetch }) => {
|
||||||
|
// const api = apiClient(fetch);
|
||||||
|
// const { data, error } = await api.GET('/v1/recipes', { ... });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// Always pass SvelteKit's `fetch` so session cookies are forwarded correctly.
|
||||||
|
|
||||||
|
export function apiClient(fetch?: typeof globalThis.fetch) {
|
||||||
|
return createClient<paths>({
|
||||||
|
baseUrl: env.BACKEND_URL ?? 'http://localhost:8080',
|
||||||
|
fetch: fetch ?? globalThis.fetch
|
||||||
|
});
|
||||||
|
}
|
||||||
24
frontend/svelte.config.js
Normal file
24
frontend/svelte.config.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
import { relative, sep } from 'node:path';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
compilerOptions: {
|
||||||
|
// defaults to rune mode for the project, except for `node_modules`. Can be removed in svelte 6.
|
||||||
|
runes: ({ filename }) => {
|
||||||
|
const relativePath = relative(import.meta.dirname, filename);
|
||||||
|
const pathSegments = relativePath.toLowerCase().split(sep);
|
||||||
|
const isExternalLibrary = pathSegments.includes('node_modules');
|
||||||
|
|
||||||
|
return isExternalLibrary ? undefined : true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
Reference in New Issue
Block a user