Skip to main content
RapidDev - Software Development Agency
lovable-issues

How to Connect a Custom Backend (Express, Django, FastAPI) to Lovable

To connect a custom backend (Express, Django, FastAPI, or any REST/GraphQL server) to a Lovable frontend, store your backend URL in Cloud tab → Secrets as a VITE_ variable, then call your API endpoints from React components using fetch. Route sensitive requests through a Supabase Edge Function to keep API keys off the frontend. Use proper CORS configuration on your backend to allow requests from your Lovable app domain.

Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced9 min read~20 minAll Lovable versionsMarch 2026RapidDev Engineering Team
TL;DR

To connect a custom backend (Express, Django, FastAPI, or any REST/GraphQL server) to a Lovable frontend, store your backend URL in Cloud tab → Secrets as a VITE_ variable, then call your API endpoints from React components using fetch. Route sensitive requests through a Supabase Edge Function to keep API keys off the frontend. Use proper CORS configuration on your backend to allow requests from your Lovable app domain.

Why connecting a custom backend to Lovable requires cross-origin and authentication handling

Lovable generates a React frontend with Supabase as the default backend. But many projects need a custom backend — an Express API for business logic, a Python server for machine learning, or a legacy system with an existing REST API. Connecting these requires understanding how the Lovable frontend communicates with external servers. The main challenge is CORS (Cross-Origin Resource Sharing). Your Lovable app runs at one domain (your-app.lovable.app or your custom domain), and your backend runs at a different domain (api.yourservice.com). Browsers block cross-origin requests by default unless the backend explicitly allows them. The second challenge is authentication. Your custom backend likely requires API keys or auth tokens that should never appear in frontend code. For public-facing endpoints, you can call them directly from React with proper CORS headers. For authenticated endpoints, route requests through a Supabase Edge Function that adds the credentials server-side. The third consideration is architecture. Decide early whether your Lovable frontend should talk to your backend directly, through Edge Functions as a proxy, or through both depending on the endpoint. A clear architecture prevents the spaghetti pattern where some calls go through Edge Functions and others go direct without any consistency.

  • CORS blocking requests from Lovable domain to custom backend server
  • Backend API keys hardcoded in frontend React code instead of using Edge Functions
  • Backend URL not stored as environment variable — breaks when deploying to different environments
  • Authentication tokens not passed correctly between Lovable frontend and custom backend
  • Mixed architecture — some API calls go direct, others through Edge Functions, with no clear pattern

Error messages you might see

Access to fetch at 'https://api.yourserver.com/data' from origin 'https://your-app.lovable.app' has been blocked by CORS policy

Your custom backend does not include CORS headers allowing requests from your Lovable domain. Add your Lovable app URL to your backend's CORS allowed origins list.

net::ERR_CONNECTION_REFUSED

The backend URL is unreachable from the browser. This usually means the backend is running on localhost (not accessible from Lovable's servers) or the server is down. Your backend must be deployed to a publicly accessible URL.

401 Unauthorized

Your backend requires authentication but the request did not include valid credentials. Either the API key is missing, the auth token has expired, or the authorization header format is wrong.

Before you start

  • A Lovable project open in the editor
  • A custom backend deployed to a publicly accessible URL (not localhost)
  • Your backend API documentation (endpoints, authentication method, request format)
  • CORS configured on your backend to accept requests from your Lovable domain

How to fix it

1

Store your backend URL as an environment variable

Hardcoding the URL breaks when you deploy to different environments (preview vs production)

Open Cloud tab → Secrets and add a new secret named VITE_API_BASE_URL with your backend's URL as the value (for example https://api.yourservice.com). Use the VITE_ prefix because this URL needs to be accessible in your frontend React code. Then create a config file that exports this URL, so all API calls reference it from one place. This makes it easy to switch between staging and production backends.

Before
typescript
// WRONG: backend URL hardcoded — breaks across environments
const response = await fetch('https://api.yourservice.com/users');
After
typescript
// CORRECT: URL from environment variable via config
// src/config/api.ts
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '';
// In your component or service:
import { API_BASE_URL } from '@/config/api';
const response = await fetch(`${API_BASE_URL}/users`);

Expected result: API calls use the environment variable, making it easy to switch between environments.

2

Configure CORS on your custom backend

Without CORS headers, the browser blocks all requests from your Lovable frontend to your backend

On your backend server, add CORS middleware that allows requests from your Lovable domain. The exact implementation depends on your backend framework. You need to allow the origin (your Lovable app URL), the methods your frontend uses (GET, POST, PUT, DELETE), and the headers (Content-Type, Authorization). For development, you can use a wildcard (*), but for production, specify the exact domains allowed.

Before
typescript
// Express backend with NO CORS — blocks all browser requests
const express = require('express');
const app = express();
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
After
typescript
// Express backend with proper CORS configuration
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: [
'https://your-app.lovable.app', // Lovable preview
'https://your-custom-domain.com', // Production
'http://localhost:8080', // Local development
],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});

Expected result: Your Lovable frontend can make requests to the backend without CORS errors.

3

Create an API service layer in your Lovable project

A service layer centralizes all backend calls in one place with consistent error handling

Create a service file that wraps all calls to your custom backend. This centralizes the base URL, authentication headers, error handling, and response parsing. Every component that needs data from your backend imports functions from this service instead of making raw fetch calls. This makes the codebase cleaner and easier to maintain.

Before
typescript
// Scattered fetch calls across components — inconsistent error handling
async function loadUsers() {
const res = await fetch('https://api.yourservice.com/users');
return res.json();
}
After
typescript
// src/services/backendApi.ts — centralized API layer
import { API_BASE_URL } from '@/config/api';
class BackendApi {
private baseUrl: string;
constructor() {
this.baseUrl = API_BASE_URL;
}
private async request<T>(path: string, options?: RequestInit): Promise<T> {
const response = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
async getUsers() {
return this.request<{ users: User[] }>('/api/users');
}
async createUser(userData: CreateUserInput) {
return this.request<User>('/api/users', {
method: 'POST',
body: JSON.stringify(userData),
});
}
}
export const backendApi = new BackendApi();

Expected result: All backend calls go through a single service with consistent error handling. Components import functions from backendApi.

4

Proxy authenticated requests through Edge Functions

Backend API keys and service credentials must stay on the server — never in browser-accessible code

For endpoints that require API keys or service-level authentication, create a Supabase Edge Function that adds the credentials and forwards the request. Your frontend calls the Edge Function (same origin, no CORS issues), and the Edge Function calls your backend with the secret credentials. Store the credentials in Cloud tab → Secrets. If this architecture requires setting up multiple proxy functions for different backend endpoints, RapidDev's engineers have implemented this exact pattern across 600+ Lovable projects.

Before
typescript
// WRONG: API key sent from the browser
const response = await fetch('https://api.yourservice.com/admin/data', {
headers: { 'X-API-Key': 'secret-admin-key-123' },
});
After
typescript
// CORRECT: Frontend calls Edge Function, Edge Function adds the key
// Frontend:
const response = await fetch('/functions/v1/backend-proxy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: '/admin/data', method: 'GET' }),
});
// Edge Function (supabase/functions/backend-proxy/index.ts):
const backendUrl = Deno.env.get('BACKEND_URL');
const apiKey = Deno.env.get('BACKEND_API_KEY');
const backendResponse = await fetch(`${backendUrl}${path}`, {
method,
headers: { 'X-API-Key': apiKey!, 'Content-Type': 'application/json' },
});

Expected result: Authenticated requests go through the Edge Function. API keys never appear in browser code or network requests.

Complete code example

src/services/backendApi.ts
1/**
2 * Centralized API service for connecting to a custom backend.
3 * Handles base URL, headers, error handling, and response parsing.
4 *
5 * Usage:
6 * import { backendApi } from '@/services/backendApi';
7 * const users = await backendApi.get<User[]>('/api/users');
8 */
9
10const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '';
11
12interface ApiOptions extends Omit<RequestInit, 'body'> {
13 body?: unknown;
14}
15
16class BackendApiService {
17 async request<T>(path: string, options: ApiOptions = {}): Promise<T> {
18 const { body, headers, ...rest } = options;
19
20 const response = await fetch(`${API_BASE_URL}${path}`, {
21 ...rest,
22 headers: {
23 'Content-Type': 'application/json',
24 ...headers,
25 },
26 body: body ? JSON.stringify(body) : undefined,
27 });
28
29 if (!response.ok) {
30 const errorText = await response.text();
31 throw new Error(
32 `Backend API error (${response.status}): ${errorText}`
33 );
34 }
35
36 return response.json();
37 }
38
39 get<T>(path: string, headers?: HeadersInit) {
40 return this.request<T>(path, { method: 'GET', headers });
41 }
42
43 post<T>(path: string, body: unknown) {
44 return this.request<T>(path, { method: 'POST', body });
45 }
46
47 put<T>(path: string, body: unknown) {
48 return this.request<T>(path, { method: 'PUT', body });
49 }
50
51 delete<T>(path: string) {
52 return this.request<T>(path, { method: 'DELETE' });
53 }
54}
55
56export const backendApi = new BackendApiService();

Best practices to prevent this

  • Store backend URLs as VITE_ environment variables — never hardcode URLs that differ between environments
  • Configure CORS on your backend with specific allowed origins for production — avoid wildcard (*) in production
  • Create a centralized API service layer so all backend calls have consistent error handling and header management
  • Route authenticated requests through Edge Functions to keep API keys and service credentials off the frontend
  • Document your API architecture clearly — which calls go direct vs through Edge Functions, and why
  • Handle loading, error, and empty states in every component that calls your backend
  • Test your backend connection from the Lovable preview domain before deploying — CORS settings must match the preview URL too

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Lovable (lovable.dev) frontend and a custom backend. I need help connecting them. My Lovable app URL: [your Lovable app domain] My backend: [framework — Express/Django/FastAPI/etc.] Backend URL: [your backend URL] Authentication: [how your backend authenticates — API key, JWT, etc.] Endpoints I need to call: [list the main endpoints] Please: 1. Show me the CORS configuration for my backend framework 2. Create a centralized API service for my Lovable frontend 3. Show me how to set up an Edge Function proxy for authenticated endpoints 4. Create a React hook for fetching data from my backend with loading/error states 5. Explain which requests should go direct vs through Edge Functions

Lovable Prompt

I need to connect my app to a custom backend at [BACKEND URL]. Create a centralized API service at src/services/backendApi.ts that uses import.meta.env.VITE_API_BASE_URL as the base URL. The service should have get, post, put, and delete methods with proper error handling and TypeScript generics. Also create an Edge Function at supabase/functions/backend-proxy/index.ts that forwards authenticated requests to the backend, reading the BACKEND_API_KEY from Secrets. Add CORS headers to the Edge Function response.

Frequently asked questions

Can I use a custom backend instead of Supabase with Lovable?

Yes. Lovable generates a React frontend that can communicate with any backend via REST or GraphQL. Store your backend URL as a VITE_ environment variable, configure CORS on your backend to allow your Lovable domain, and create an API service layer in your frontend to make the calls.

How do I connect an Express or Node.js backend to Lovable?

Deploy your Express server to a publicly accessible URL (not localhost). Add the cors middleware with your Lovable domain in the allowed origins. In Lovable, add VITE_API_BASE_URL pointing to your server URL in Cloud tab → Secrets. Create a service file in src/services/ that wraps your API calls.

Can my backend run on localhost and connect to Lovable?

No. Lovable runs in the cloud, so your backend must be deployed to a publicly accessible URL. Localhost is only reachable from your own computer. Deploy your backend to a service like Railway, Render, Fly.io, or AWS before connecting it to Lovable.

How do I handle authentication between Lovable and my custom backend?

For public endpoints: call them directly from React with proper CORS. For authenticated endpoints: route requests through a Supabase Edge Function that reads credentials from Secrets and adds them to the request. Never put API keys or service credentials in frontend code.

Should I use REST or GraphQL with Lovable?

Both work. REST is simpler to set up — just use fetch with your API endpoints. GraphQL requires a client library like urql or Apollo. Since Lovable AI understands both patterns, prompt it to create the integration layer in whichever style matches your backend.

How do I handle CORS when connecting Lovable to my backend?

On your backend, add CORS middleware that allows requests from your Lovable domain (both the lovable.app preview URL and your custom domain). Allow the HTTP methods and headers your frontend uses. For development, you can temporarily use wildcard (*) but switch to specific origins for production.

What if I need help connecting a complex custom backend?

Integrating custom backends with authentication, CORS, Edge Function proxies, and proper error handling involves coordinating multiple layers. RapidDev's engineers have connected custom backends to 600+ Lovable frontends and can architect the integration correctly from the start.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your issue.

Book a free consultation

Need help with your Lovable project?

Our experts have built 600+ apps and can solve your issue fast. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.