encore-frontend
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrontend Integration with Encore
前端与Encore的集成
Instructions
操作步骤
Encore provides tools to connect your frontend applications to your backend APIs.
Encore 提供了将前端应用连接到后端API的工具。
Generate a TypeScript Client
生成TypeScript客户端
bash
undefinedbash
undefinedGenerate client for local development
Generate client for local development
encore gen client --output=./frontend/src/client.ts --env=local
encore gen client --output=./frontend/src/client.ts --env=local
Generate client for a deployed environment
Generate client for a deployed environment
encore gen client --output=./frontend/src/client.ts --env=staging
This generates a fully typed client based on your API definitions.encore gen client --output=./frontend/src/client.ts --env=staging
这会根据你的API定义生成一个完全类型化的客户端。Using the Generated Client
使用生成的客户端
typescript
// frontend/src/client.ts is auto-generated
import Client from "./client";
const client = new Client("http://localhost:4000");
// Fully typed API calls
const user = await client.user.getUser({ id: "123" });
console.log(user.email);
const newUser = await client.user.createUser({
email: "new@example.com",
name: "New User",
});typescript
// frontend/src/client.ts is auto-generated
import Client from "./client";
const client = new Client("http://localhost:4000");
// Fully typed API calls
const user = await client.user.getUser({ id: "123" });
console.log(user.email);
const newUser = await client.user.createUser({
email: "new@example.com",
name: "New User",
});React Example
React示例
tsx
// frontend/src/components/UserProfile.tsx
import { useState, useEffect } from "react";
import Client from "../client";
const client = new Client(import.meta.env.VITE_API_URL);
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
client.user.getUser({ id: userId })
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}tsx
// frontend/src/components/UserProfile.tsx
import { useState, useEffect } from "react";
import Client from "../client";
const client = new Client(import.meta.env.VITE_API_URL);
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
client.user.getUser({ id: userId })
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}React with TanStack Query
React与TanStack Query结合使用
tsx
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import Client from "../client";
const client = new Client(import.meta.env.VITE_API_URL);
export function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ["user", userId],
queryFn: () => client.user.getUser({ id: userId }),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
export function CreateUserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: { email: string; name: string }) =>
client.user.createUser(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
mutation.mutate({
email: formData.get("email") as string,
name: formData.get("name") as string,
});
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="name" required />
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "Creating..." : "Create User"}
</button>
</form>
);
}tsx
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import Client from "../client";
const client = new Client(import.meta.env.VITE_API_URL);
export function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ["user", userId],
queryFn: () => client.user.getUser({ id: userId }),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
export function CreateUserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: { email: string; name: string }) =>
client.user.createUser(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
mutation.mutate({
email: formData.get("email") as string,
name: formData.get("name") as string,
});
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="name" required />
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "Creating..." : "Create User"}
</button>
</form>
);
}Next.js Server Components
Next.js服务器组件
tsx
// app/users/[id]/page.tsx
import Client from "@/lib/client";
const client = new Client(process.env.API_URL);
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await client.user.getUser({ id: params.id });
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}tsx
// app/users/[id]/page.tsx
import Client from "@/lib/client";
const client = new Client(process.env.API_URL);
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await client.user.getUser({ id: params.id });
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}CORS Configuration
CORS配置
Configure CORS in your file:
encore.appcue
{
"id": "my-app",
"global_cors": {
"allow_origins_with_credentials": [
"http://localhost:3000",
"https://myapp.com",
"https://*.myapp.com"
]
}
}在你的文件中配置CORS:
encore.appcue
{
"id": "my-app",
"global_cors": {
"allow_origins_with_credentials": [
"http://localhost:3000",
"https://myapp.com",
"https://*.myapp.com"
]
}
}CORS Options
CORS选项
| Option | Description |
|---|---|
| Origins allowed for non-credentialed requests (default: |
| Origins allowed for credentialed requests (cookies, auth headers) |
| Additional request headers to allow |
| Additional response headers to expose |
| Enable CORS debug logging |
| 选项 | 描述 |
|---|---|
| 允许发起无凭据请求的源(默认: |
| 允许发起带凭据请求的源(Cookie、认证头) |
| 允许的额外请求头 |
| 允许暴露的额外响应头 |
| 启用CORS调试日志 |
Authentication from Frontend
前端认证
typescript
// With the generated client
const client = new Client("http://localhost:4000");
// Set auth token
client.setAuthToken(userToken);
// Now all requests include the Authorization header
const profile = await client.user.getProfile();Or manually with fetch:
typescript
const response = await fetch("http://localhost:4000/profile", {
headers: {
"Authorization": `Bearer ${token}`,
},
});typescript
// With the generated client
const client = new Client("http://localhost:4000");
// Set auth token
client.setAuthToken(userToken);
// Now all requests include the Authorization header
const profile = await client.user.getProfile();或者手动使用fetch:
typescript
const response = await fetch("http://localhost:4000/profile", {
headers: {
"Authorization": `Bearer ${token}`,
},
});Using Plain Fetch
使用原生Fetch
If you prefer not to use the generated client:
typescript
async function getUser(id: string) {
const response = await fetch(`http://localhost:4000/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}
async function createUser(email: string, name: string) {
const response = await fetch("http://localhost:4000/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, name }),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}如果你不想使用生成的客户端:
typescript
async function getUser(id: string) {
const response = await fetch(`http://localhost:4000/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}
async function createUser(email: string, name: string) {
const response = await fetch("http://localhost:4000/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, name }),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}Environment Variables
环境变量
bash
undefinedbash
undefined.env.local (Next.js)
.env.local (Next.js)
NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_API_URL=http://localhost:4000
.env (Vite)
.env (Vite)
VITE_API_URL=http://localhost:4000
undefinedVITE_API_URL=http://localhost:4000
undefinedGuidelines
指南
- Use to generate typed API clients
encore gen client - Regenerate the client when your API changes
- Configure CORS in for production domains
encore.app - Use for authenticated requests
allow_origins_with_credentials - Set auth tokens on the client for protected endpoints
- Use environment variables for API URLs (different per environment)
- The generated client handles errors and types automatically
- 使用生成类型化API客户端
encore gen client - 当API变更时重新生成客户端
- 在中为生产环境域名配置CORS
encore.app - 对带认证的请求使用
allow_origins_with_credentials - 为受保护的端点在客户端上设置认证令牌
- 使用环境变量存储API URL(不同环境使用不同值)
- 生成的客户端会自动处理错误和类型