Modern React frontend for the Worlddriven admin interface, built with Vite and a proxy-only Express server.
This webapp follows a proxy-only pattern:
- Frontend: React 19 + Vite for fast development and modern UI
- Backend Proxy: Thin Express server that forwards API requests to worlddriven/core
- Authentication: Converts httpOnly cookies to Authorization headers for security
- No Database: All data and business logic lives in worlddriven/core backend
- Security: Authentication tokens stored in httpOnly cookies (not accessible to JavaScript)
- Separation: Frontend completely decoupled from backend business logic
- Simplicity: Webapp is just UI + proxy, no database or complex server logic
- Flexibility: Easy to swap backends or deploy frontend separately
- React 19: Latest React with modern features
- Vite: Lightning-fast build tool and dev server
- React Router DOM: Client-side routing
- Styled Components: Component-scoped CSS-in-JS
- TypeScript: Type checking via JSDoc (no transpilation)
- Express 5: Minimal server for proxying requests
- Vite Middleware: Integrated dev server with HMR
- ESLint: Linting with recommended React rules
- Prettier: Code formatting
- TypeScript: Type checking without compilation (JSDoc)
- Node.js 18+ and npm
- Access to worlddriven/core backend API
npm installStart the development server with Vite HMR:
npm startThe webapp will be available at http://localhost:3000 (or the port specified in PORT environment variable).
Create a .env file in the root directory:
# Backend API URL
BACKEND_URL=http://localhost:8080
# Server port (default: 3000)
PORT=3000
# Node environment
NODE_ENV=developmentRuns the development server with Vite middleware and hot module replacement.
Builds the frontend for production to the dist/ directory.
Runs ESLint to check for code quality issues.
Automatically formats code with Prettier, fixes ESLint issues, and type-checks with TypeScript.
Checks code formatting, linting, and types without making changes.
webapp/
├── server/ # Express proxy server
│ ├── index.js # Main server entry point
│ └── proxy.js # API proxy with cookie→header conversion
├── src/ # React frontend source
│ ├── components/ # Reusable UI components
│ │ ├── Header.jsx
│ │ ├── Footer.jsx
│ │ └── Layout.jsx
│ ├── pages/ # Page components
│ │ └── Home/
│ ├── App.jsx # Main app component with routing
│ └── main.jsx # React entry point
├── public/ # Static assets
│ └── theme.css # Global CSS variables and reset
├── index.html # HTML template
├── vite.config.js # Vite configuration
├── eslint.config.js # ESLint configuration
├── tsconfig.json # TypeScript/JSDoc configuration
└── package.json # Dependencies and scripts
The proxy server (server/proxy.js) converts httpOnly cookies to Authorization headers:
-
Login Request: User authenticates via
/api/auth/login- Backend returns
sessionIdin response body - Proxy extracts
sessionIdand sets it as httpOnly cookie - Removes
sessionIdfrom response to prevent JavaScript access
- Backend returns
-
Authenticated Requests: Subsequent API calls
- Proxy extracts
sessionIdfrom httpOnly cookie - Converts to
Authorization: SESSION <sessionId>header - Forwards request to backend with authorization
- Proxy extracts
-
Logout Request: User logs out via
/api/auth/logout- Proxy clears the httpOnly cookie
- XSS Protection: JavaScript cannot access httpOnly cookies
- CSRF Protection: Combined with sameSite=strict attribute
- Automatic: Browser handles cookie storage and sending
- Create page component in
src/pages/ - Add route in
src/App.jsx - Optionally add navigation link in
src/components/Header.jsx
Example:
// src/pages/Repositories/index.jsx
function Repositories() {
return <div>Repositories page</div>;
}
export default Repositories;
// src/App.jsx
import Repositories from './pages/Repositories';
<Route path="/repositories" element={<Repositories />} />;Create components in src/components/ and use styled-components for styling:
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: var(--color-primary);
color: white;
padding: var(--spacing-md);
border-radius: var(--radius-md);
`;
function Button({ children, ...props }) {
return <StyledButton {...props}>{children}</StyledButton>;
}
export default Button;Use JSDoc comments for TypeScript type checking:
/**
* Fetches repositories from the API
* @param {string} userId - The user ID
* @returns {Promise<Array<{id: string, name: string}>>} Array of repositories
*/
async function fetchRepositories(userId) {
// ...
}npm run buildThis creates an optimized production build in the dist/ directory.
NODE_ENV=production npm startThe production server serves the built static files and proxies API requests.
For production, ensure these environment variables are set:
NODE_ENV=production
BACKEND_URL=https://api.worlddriven.org
PORT=3000The project uses ESLint with React-specific rules:
npm run lintPrettier is configured for consistent code formatting:
npx prettier --write .TypeScript compiler checks JSDoc types without transpilation:
npx tsc --noEmitRun all quality checks at once:
npm run checkThis project follows the Worlddriven philosophy of democratic development. Changes are proposed via pull requests and merged through time-based voting.
See the worlddriven/documentation repository for more information about the democratic development process.
This project is part of the Worlddriven organization and follows the same license as the core project.