encore-frontend

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frontend 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
undefined
bash
undefined

Generate 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
encore.app
file:
cue
{
    "id": "my-app",
    "global_cors": {
        "allow_origins_with_credentials": [
            "http://localhost:3000",
            "https://myapp.com",
            "https://*.myapp.com"
        ]
    }
}
在你的
encore.app
文件中配置CORS:
cue
{
    "id": "my-app",
    "global_cors": {
        "allow_origins_with_credentials": [
            "http://localhost:3000",
            "https://myapp.com",
            "https://*.myapp.com"
        ]
    }
}

CORS Options

CORS选项

OptionDescription
allow_origins_without_credentials
Origins allowed for non-credentialed requests (default:
["*"]
)
allow_origins_with_credentials
Origins allowed for credentialed requests (cookies, auth headers)
allow_headers
Additional request headers to allow
expose_headers
Additional response headers to expose
debug
Enable CORS debug logging
选项描述
allow_origins_without_credentials
允许发起无凭据请求的源(默认:
["*"]
allow_origins_with_credentials
允许发起带凭据请求的源(Cookie、认证头)
allow_headers
允许的额外请求头
expose_headers
允许暴露的额外响应头
debug
启用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
undefined
bash
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)

undefined
undefined

Guidelines

指南

  • Use
    encore gen client
    to generate typed API clients
  • Regenerate the client when your API changes
  • Configure CORS in
    encore.app
    for production domains
  • Use
    allow_origins_with_credentials
    for authenticated requests
  • 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
  • 使用
    encore gen client
    生成类型化API客户端
  • 当API变更时重新生成客户端
  • encore.app
    中为生产环境域名配置CORS
  • 对带认证的请求使用
    allow_origins_with_credentials
  • 为受保护的端点在客户端上设置认证令牌
  • 使用环境变量存储API URL(不同环境使用不同值)
  • 生成的客户端会自动处理错误和类型