From 719a1e81d5af5c872f79f11f462e4b30211f80b7 Mon Sep 17 00:00:00 2001 From: IwanIDev Date: Fri, 20 Mar 2026 13:39:53 +0000 Subject: Add local development support with Docker for Drupal and configure API proxy --- .env.example | 4 +- README.md | 31 ++++++++++++++ docker-compose.local.yml | 37 ++++++++++++++++ src/App.tsx | 107 ++++++++++++++++++++++++++++++++++++----------- vite.config.ts | 9 ++++ 5 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 docker-compose.local.yml diff --git a/.env.example b/.env.example index df591d0..fece7bb 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ -VITE_DRUPAL_BASE_URL=https://your-drupal-domain.tld -VITE_DRUPAL_API_PREFIX=/jsonapi +VITE_DRUPAL_BASE_URL=http://localhost:5173 +VITE_DRUPAL_API_PREFIX=/drupal-api/jsonapi VITE_DRUPAL_AUTH_TOKEN= diff --git a/README.md b/README.md index 5e16435..a059811 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ Available variables: - `VITE_DRUPAL_API_PREFIX` (optional) – defaults to `/jsonapi` - `VITE_DRUPAL_AUTH_TOKEN` (optional) – bearer token used by the HTTP client +For local development with the included Drupal stack (CORS-safe via Vite proxy), use: + +- `VITE_DRUPAL_BASE_URL=http://localhost:5173` +- `VITE_DRUPAL_API_PREFIX=/drupal-api/jsonapi` + ## Client utilities - Typed env config: `src/config/env.ts` @@ -47,6 +52,32 @@ bun install bun run dev ``` +## Local Drupal server (for testing) + +Start local Drupal + MariaDB: + +```bash +docker compose -f docker-compose.local.yml up -d +``` + +Then open Drupal installer: + +- `http://localhost:8081` + +After installation: + +1. Enable JSON:API module in Drupal (if not already enabled). +2. Create at least one Article content item. +3. Keep frontend env values on proxy mode (`http://localhost:5173` + `/drupal-api/jsonapi`). + +Run frontend: + +```bash +bun run dev +``` + +Your React app requests `/drupal-api/...` on the Vite dev server, and Vite proxies to Drupal at `http://localhost:8081`, avoiding browser CORS issues. + ## Docker build and run Pass Drupal variables at build time (Vite injects `VITE_*` during build): diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..fc7b4ad --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,37 @@ +services: + drupal: + image: drupal:10-apache + container_name: portfolio-drupal + ports: + - '8081:80' + environment: + DRUPAL_DB_HOST: mariadb + DRUPAL_DB_PORT: 3306 + DRUPAL_DB_NAME: ${DRUPAL_DB_NAME:-drupal} + DRUPAL_DB_USER: ${DRUPAL_DB_USER:-drupal} + DRUPAL_DB_PASSWORD: ${DRUPAL_DB_PASSWORD:-drupal} + depends_on: + - mariadb + volumes: + - drupal-sites:/var/www/html/sites + - drupal-modules:/var/www/html/modules + - drupal-themes:/var/www/html/themes + restart: unless-stopped + + mariadb: + image: mariadb:11 + container_name: portfolio-drupal-db + environment: + MARIADB_DATABASE: ${DRUPAL_DB_NAME:-drupal} + MARIADB_USER: ${DRUPAL_DB_USER:-drupal} + MARIADB_PASSWORD: ${DRUPAL_DB_PASSWORD:-drupal} + MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-root} + volumes: + - mariadb-data:/var/lib/mysql + restart: unless-stopped + +volumes: + drupal-sites: + drupal-modules: + drupal-themes: + mariadb-data: \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index aaeba0a..3472fd0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,34 +1,91 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' +import { useEffect, useState } from 'react' +import { fetchDrupalResource } from './lib/drupalClient' import './App.css' +type DrupalArticle = { + id: string + attributes?: { + title?: string + status?: boolean + created?: string + } +} + +type DrupalCollectionResponse = { + data: T[] +} + +type DrupalJsonApiEntryPoint = { + links: Record +} + function App() { - const [count, setCount] = useState(0) + const [articles, setArticles] = useState([]) + const [resourcePath, setResourcePath] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const loadArticles = async () => { + try { + setIsLoading(true) + setError(null) + + const entryPoint = await fetchDrupalResource('') + const nodeResources = Object.keys(entryPoint.links).filter((key) => key.startsWith('node--')) + + if (nodeResources.length === 0) { + throw new Error('No node resources found in JSON:API. Create a content type (for example Article) and ensure JSON:API is enabled.') + } + + const selectedNodeResource = nodeResources.includes('node--article') ? 'node--article' : nodeResources[0] + const selectedPath = `/${selectedNodeResource.replace('--', '/')}` + + setResourcePath(selectedPath) + + const response = await fetchDrupalResource>(selectedPath) + setArticles(response.data) + } catch (loadError) { + const message = loadError instanceof Error ? loadError.message : 'Failed to load articles from Drupal.' + setError(message) + } finally { + setIsLoading(false) + } + } + + void loadArticles() + }, []) return ( - <> - -

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR +

+

Articles

+ {!isLoading && !error && resourcePath &&

Using resource: {resourcePath}

} + + {isLoading &&

Loading articles…

} + + {error && ( +

+ Could not fetch articles: {error}

-
-

- Click on the Vite and React logos to learn more -

- + )} + + {!isLoading && !error && articles.length === 0 && ( +

Connected to Drupal, but no articles were returned.

+ )} + + {!isLoading && !error && articles.length > 0 && ( +
    + {articles.map((article) => ( +
  • + {article.attributes?.title ?? '(Untitled)'} +
    ID: {article.id}
    +
    Status: {article.attributes?.status ? 'Published' : 'Unpublished'}
    + {article.attributes?.created &&
    Created: {new Date(article.attributes.created).toLocaleString()}
    } +
  • + ))} +
+ )} + ) } diff --git a/vite.config.ts b/vite.config.ts index 149038b..32cbb6c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,4 +12,13 @@ export default defineConfig({ }), tailwindcss(), ], + server: { + proxy: { + '/drupal-api': { + target: 'http://localhost:8081', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/drupal-api/, ''), + }, + }, + }, }) -- cgit