diff --git a/src/routes/blog/post/announcing-appwrite-react-library/+page.markdoc b/src/routes/blog/post/announcing-appwrite-react-library/+page.markdoc new file mode 100644 index 00000000000..8e1a23d1853 --- /dev/null +++ b/src/routes/blog/post/announcing-appwrite-react-library/+page.markdoc @@ -0,0 +1,136 @@ +--- +layout: post +title: "Announcing the Appwrite React library" +description: A small set of React hooks and SSR adapters that take the busywork out of wiring Appwrite Auth into Vite, Next.js, and TanStack Start apps. +date: 2026-05-25 +cover: /images/blog/announcing-appwrite-react-library/cover.avif +timeToRead: 5 +author: atharva +category: announcement, authentication +faqs: + - question: 'What is the Appwrite React library?' + answer: 'It is an official React package that wraps the Appwrite Web SDK with a `Provider` and hooks for auth state. It also ships SSR adapters for Next.js and TanStack Start so the same hooks work in server-rendered apps. See the [React library docs](/docs/products/auth/react).' + - question: 'Do I need this if I already use the Appwrite Web SDK?' + answer: 'No, the Web SDK alone still works. The library is for teams who want pre-built hooks, less boilerplate around session hydration, and a turnkey SSR auth flow. If your app is small or fully client-rendered, the Web SDK on its own is fine.' + - question: 'Which React versions are supported?' + answer: 'React 18 and React 19, on both client-rendered apps and SSR frameworks. Next.js 15+ and TanStack Start are supported out of the box.' + - question: 'How does SSR auth work under the hood?' + answer: 'You mount one handler route per framework. The handler creates sessions with a server API key and writes an HTTP-only cookie. On the server, helpers read the cookie and return the user; on the client, the same cookie hydrates the Web SDK so hooks return the user without an extra round trip.' + - question: 'Does the library cover more than authentication?' + answer: 'Not yet. The first release focuses on the auth surface (sign-up, sign-in, sign-out, OAuth, current user). Database and storage hooks are on the roadmap.' + - question: 'Where do I start?' + answer: 'Pick the framework you are using: [Vite React](/docs/quick-starts/react), [Next.js](/docs/quick-starts/nextjs), or [TanStack Start](/docs/quick-starts/tanstack-start). Each quickstart walks through install, configuration, and a working sign-up flow.' +--- + +Every React developer who has wired Appwrite into a project knows the drill. You install the Web SDK, write a small `appwrite.ts` file with a `Client` and an `Account`, and then build the same `useUser` hook and sign-in form you have built for every other project. For client-only apps that is fine, but the moment SSR enters the picture (`cookies()` in Next.js, server functions in TanStack Start) you also start hand-rolling session cookies, server helpers, and the `setSession` plumbing that hydrates the client from the request. + +Today, we are releasing the [Appwrite React library](/docs/products/auth/react), an official package that gives you that whole layer out of the box. + +# The provider and hooks + +The library exposes a single `AppwriteProvider` and five auth hooks: + +- `useAuth` for the combined user, sign-in, sign-up, and sign-out surface +- `useUser` for the current authenticated user +- `useSignIn` for email/password and OAuth sign-in +- `useSignUp` for account creation +- `useSignOut` for ending the current session + +It is built on top of the existing [Appwrite Web SDK](/docs/sdks) and uses TanStack Query underneath, so cached user state stays in sync across components automatically. + +What makes it different from a hand-rolled `useUser` is what happens on the server. The library ships dedicated entrypoints for Next.js App Router and TanStack Start that handle the cookie, hydration, and admin-client wiring for you, so the hooks behave the same in a Vite SPA as they do in a server-rendered page. + +# Client-side setup + +For a Vite app, setup is two imports. Wrap the tree with the provider, then call `useAuth` wherever you need it. + +```tsx +// main.tsx +import { AppwriteProvider } from "@appwrite.io/react"; + + + +; +``` + +With the provider in place, every component can pull the current user, sign-in, sign-up, and sign-out methods from a single hook. + +```tsx +// App.tsx +import { useAuth } from "@appwrite.io/react"; + +function App() { + const { user, signIn, signOut } = useAuth(); + // render auth UI +} +``` + +That is the entire client setup. The library handles the service file, the initial account fetching on mount, and the auth context that you would otherwise write by hand. The full walkthrough lives in the [Vite React quickstart](/docs/quick-starts/react). + +# Server-side rendering + +For server-rendered React, the package adds three pieces: + +- A handler route that processes sign-in, sign-up, sign-out, and OAuth callback requests on your server. +- Server helpers that resolve the current user, session, or session-scoped client from the request cookie. +- An SSR mode on the provider that hydrates the client with the server-side session, so the first paint already knows the user. + +Concretely, in a Next.js layout, you read the cookie server-side, pass the value through the provider, and the client hooks pick it up. + +```tsx +// app/layout.tsx +const helpers = createNextServerHelpers(appwrite); +const session = await helpers.readSessionCookie(); + +return {children}; +``` + +The same helpers can be called inside any server component to fetch the user before the page is sent to the browser. + +```tsx +// app/page.tsx +const user = await helpers.getLoggedInUser(); +``` + +The same flow on TanStack Start runs through `createServerFn` and `Route.useLoaderData()`. The hooks themselves do not change. + +# Session client + +For per-request operations on behalf of the signed-in user, the framework helpers expose `createSessionClient`. It returns a `node-appwrite` client already authenticated with the request cookie, so server components and loaders can act as the user without re-implementing session handling. + +```ts +const helpers = createNextServerHelpers(appwrite); +const session = await helpers.createSessionClient(); + +if (session) { + const user = await session.account.get(); +} +``` + +# Admin client + +Server entrypoints also expose `createAdminClient`, which returns a `node-appwrite` client authenticated with your project API key. It is the same client you would write yourself, just with the environment wiring done for you, so the rest of your server code keeps using the official Node SDK. + +```ts +const admin = createAdminClient({ + ...appwrite, + apiKey: process.env.APPWRITE_API_KEY!, +}); + +const users = await admin.account.list(); +``` + +# Where to start + +Three quickstarts ship alongside the library: + +- [Vite React](/docs/quick-starts/react) for client-rendered apps +- [Next.js App Router](/docs/quick-starts/nextjs) for server-rendered React on Next.js +- [TanStack Start](/docs/quick-starts/tanstack-start) for server-rendered React on TanStack Start + +Each one ends with a working sign-up, sign-in, and sign-out flow. From there, the [React library docs](/docs/products/auth/react) cover the hooks, the server helpers, OAuth, and the handler configuration in depth. + +We would love to hear what you build with it. Drop into the [Appwrite Discord](https://appwrite.io/discord) and tell us how the SSR story feels in your framework of choice. diff --git a/src/routes/docs/products/auth/+layout.svelte b/src/routes/docs/products/auth/+layout.svelte index 799c2019496..67bc9b0f3ff 100644 --- a/src/routes/docs/products/auth/+layout.svelte +++ b/src/routes/docs/products/auth/+layout.svelte @@ -104,6 +104,11 @@ label: 'SSR login', href: '/docs/products/auth/server-side-rendering' }, + { + label: 'React library', + href: '/docs/products/auth/react', + new: isNewUntil('30 June 2026') + }, { label: 'Custom token login', href: '/docs/products/auth/custom-token' diff --git a/src/routes/docs/products/auth/react/+page.markdoc b/src/routes/docs/products/auth/react/+page.markdoc new file mode 100644 index 00000000000..ff8498f6a20 --- /dev/null +++ b/src/routes/docs/products/auth/react/+page.markdoc @@ -0,0 +1,379 @@ +--- +layout: article +title: React library +description: Add authentication to React apps with Appwrite's official React library. Supports client-side React, Next.js, and TanStack Start with a single provider and a small set of hooks. +--- + +The Appwrite React library is a thin layer over the [Web SDK](/docs/sdks) that exposes a provider and a small set of hooks for authentication operations and current user state. It works in both client-rendered React apps and server-rendered apps on Next.js and TanStack Start. + +# Why use it {% #why %} + +- **SSR auth without boilerplate.** Drop in one handler route per framework and skip the days normally spent writing cookie logic, session sync, and server/client hydration. +- **Consistent user state across server and client.** Server components, loaders, and client hooks return the same authenticated user, removing the need to reconcile auth state across the render boundary. +- **Server-side access when you need it.** Read the current user, create per-request session clients, or reach for an admin client without hand-wiring the Node SDK on every route. + +# Install {% #install %} + +Install the library along with the **Appwrite Web SDK** and **TanStack Query** packages. + +```sh +npm install @appwrite.io/react appwrite @tanstack/react-query +``` + +For SSR apps on Next.js or TanStack Start, also install `node-appwrite`. The SSR handlers create sessions with a server API key. + +```sh +npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query +``` + +# Pick your framework {% #frameworks %} + +{% cards %} +{% cards_item href="/docs/quick-starts/react" title="Client-side React" %} +For Vite and other client-rendered React apps. +{% /cards_item %} +{% cards_item href="/docs/quick-starts/nextjs" title="Next.js" %} +For server-rendered apps on the Next.js App Router. +{% /cards_item %} +{% cards_item href="/docs/quick-starts/tanstack-start" title="TanStack Start" %} +For server-rendered apps on TanStack Start. +{% /cards_item %} +{% /cards %} + +# The provider {% #provider %} + +Every app starts with an `AppwriteProvider`. In a CSR app, only `endpoint` and `projectId` are required. + +```tsx +import { AppwriteProvider } from "@appwrite.io/react"; + + + +; +``` + +In an SSR app, pass an `ssr` prop with the session secret read from the server-side cookie and the path your handlers are mounted at. + +```tsx + + {children} + +``` + +When `ssr` is set, sign-in, sign-up, and sign-out mutations route through your handler. The underlying Web SDK is hydrated with the session secret on first render so authenticated reads do not need a round trip. + +# Hooks {% #hooks %} + +The library exports one combined hook and four focused mutation hooks. All hooks share the same TanStack Query cache, so updating user state from any of them reflects everywhere. + +## useAuth {% #use-auth %} + +```tsx +import { useAuth } from "@appwrite.io/react"; + +const { user, isLoading, error, refresh, signIn, signUp, signOut } = useAuth(); +``` + +Returns: + +| Field | Type | Description | +| --- | --- | --- | +| `user` | `Models.User \| null \| undefined` | The current user, or `null` if signed out. `undefined` while loading. | +| `isLoading` | `boolean` | True while the initial user fetch is in flight. | +| `error` | `Error \| null` | First error from the user query or any mutation. | +| `refresh` | `() => Promise` | Refetch the current user and return the resolved value. | +| `signIn` | `ReturnType` | Sign-in mutation handle. | +| `signUp` | `ReturnType` | Sign-up mutation handle. | +| `signOut` | `ReturnType` | Sign-out mutation handle. | + +## useUser {% #use-user %} + +```tsx +import { useUser } from "@appwrite.io/react"; + +const { user, isLoading, error, refresh } = useUser(); +``` + +Returns: + +| Field | Type | Description | +| --- | --- | --- | +| `user` | `Models.User \| null \| undefined` | The current user, or `null` if signed out. `undefined` while loading. | +| `isLoading` | `boolean` | True while the user is being fetched. | +| `error` | `Error \| null` | Error from the fetch, if one occurred. | +| `refresh` | `() => Promise` | Refetch the current user and return the resolved value. | + +## useSignIn {% #use-sign-in %} + +```tsx +const { emailPassword, oAuth, isPending, error } = useSignIn(); + +emailPassword({ email, password, onSuccess, onError }); +oAuth({ provider: OAuthProvider.Google, successUrl, failureUrl, scopes }); +``` + +Returns: + +| Field | Type | Description | +| --- | --- | --- | +| `emailPassword` | `function` | Trigger an email and password sign-in. | +| `oAuth` | `function` | Trigger an OAuth sign-in. | +| `isPending` | `boolean` | True while an email and password sign-in is in flight. | +| `error` | `Error \| null` | Error from the last email and password sign-in attempt. | + +Parameters for `emailPassword`: + +| Parameter | Required | Description | +| --- | --- | --- | +| `email` | Yes | User's email address. | +| `password` | Yes | User's password. | +| `onSuccess` | No | Callback invoked with the signed-in user. | +| `onError` | No | Callback invoked with the thrown error. | + +Parameters for `oAuth`: + +| Parameter | Required | Description | +| --- | --- | --- | +| `provider` | Yes | An `OAuthProvider` value re-exported by the library. | +| `successUrl` | No | URL to redirect to after successful login (CSR only). | +| `failureUrl` | No | URL to redirect to after a failed attempt. | +| `scopes` | No | Provider-specific OAuth scopes to request. | +| `onError` | No | Callback invoked if the request fails before redirect. | + +In SSR mode the success URL is fixed to the handler callback. The final redirect after that callback is controlled by the `redirects.success` config on the handler. + +## useSignUp {% #use-sign-up %} + +```tsx +const { emailPassword, isPending, error } = useSignUp(); + +emailPassword({ email, password, name, userId, onSuccess, onError }); +``` + +Returns: + +| Field | Type | Description | +| --- | --- | --- | +| `emailPassword` | `function` | Create an account with email and password, then sign the user in. | +| `isPending` | `boolean` | True while the sign-up is in flight. | +| `error` | `Error \| null` | Error from the last sign-up attempt. | + +Parameters for `emailPassword`: + +| Parameter | Required | Description | +| --- | --- | --- | +| `email` | Yes | New user's email address. | +| `password` | Yes | New user's password. | +| `name` | No | Display name for the user. | +| `userId` | No | Custom user ID. Defaults to `ID.unique()`. | +| `onSuccess` | No | Callback invoked with the created user. | +| `onError` | No | Callback invoked with the thrown error. | + +## useSignOut {% #use-sign-out %} + +```tsx +const { signOut, isPending, error } = useSignOut(); + +signOut({ onSuccess, onError }); +``` + +Returns: + +| Field | Type | Description | +| --- | --- | --- | +| `signOut` | `function` | End the current user's session. | +| `isPending` | `boolean` | True while the sign-out is in flight. | +| `error` | `Error \| null` | Error from the last sign-out attempt. | + +Parameters for `signOut`: + +| Parameter | Required | Description | +| --- | --- | --- | +| `onSuccess` | No | Callback invoked after the session is destroyed. | +| `onError` | No | Callback invoked with the thrown error. | + +After a successful sign-out, all cached auth queries are dropped and the local Web SDK client is reset. On Next.js, call `router.refresh()` from `onSuccess` to re-render server components with the cleared cookie. On TanStack Start, call `router.invalidate()`. + +## useAppwrite {% #use-appwrite %} + +```tsx +import { useAppwrite } from "@appwrite.io/react"; + +const { client, account, tablesDB, storage, teams, ssr } = useAppwrite(); +``` + +Returns the underlying provider context with every Appwrite Web SDK service instance bound to the configured client. Use it when you need direct access to a service the higher-level hooks do not wrap (databases, storage, messaging, realtime, and so on). + +Returns: + +| Field | Type | Description | +| --- | --- | --- | +| `client` | `Client` | The Appwrite Web SDK `Client` configured by the provider. | +| `account` | `Account` | Web SDK `Account` service. | +| `avatars` | `Avatars` | Web SDK `Avatars` service. | +| `functions` | `Functions` | Web SDK `Functions` service. | +| `graphql` | `Graphql` | Web SDK `Graphql` service. | +| `locale` | `Locale` | Web SDK `Locale` service. | +| `messaging` | `Messaging` | Web SDK `Messaging` service. | +| `presences` | `Presences` | Web SDK `Presences` service. | +| `realtime` | `Realtime` | Web SDK `Realtime` service. | +| `storage` | `Storage` | Web SDK `Storage` service. | +| `tablesDB` | `TablesDB` | Web SDK `TablesDB` service. | +| `teams` | `Teams` | Web SDK `Teams` service. | +| `authenticated` | `boolean` | Whether the provider currently considers a user signed in. | +| `setAuthenticated` | `Dispatch>` | Update the authenticated flag manually. | +| `ssr.enabled` | `boolean` | Whether the provider was mounted with the `ssr` prop. | +| `ssr.basePath` | `string` | Mount path of the handler routes. | +| `ssr.session` | `string \| null` | Session secret read from the server cookie. | + +Throws if called outside an `AppwriteProvider`. + +# Server helpers {% #server-helpers %} + +The library ships server entrypoints scoped per framework. Each helper exposes the same surface, differing only in how it reads the request cookie. + +```ts +// Next.js (App Router) +import { createNextServerHelpers } from "@appwrite.io/react/server/next"; + +// TanStack Start +import { createTanStackServerHelpers } from "@appwrite.io/react/server/tanstack"; +``` + +Both return an object with: + +| Method | Returns | Use it for | +| --- | --- | --- | +| `readSessionCookie()` | `string \| undefined` (sync on TanStack, async on Next.js) | The session secret. Pass it into `AppwriteProvider`'s `ssr.session` prop. | +| `getLoggedInUser()` | `Models.User \| null` | The current user, fetched via the cookie. Returns `null` on 401. | +| `getSession()` | `Models.Session \| null` | The current session row. | +| `createSessionClient()` | `{ client, account } \| null` | A `node-appwrite` client authenticated as the cookie's user, or `null` if no cookie. | + +## Session client {% #session-client %} + +For per-request operations scoped to the current user (reading their data, calling APIs on their behalf) call `createSessionClient` from the framework helper. It returns a `node-appwrite` client already authenticated with the session cookie, ready to use in server components, loaders, and server functions. + +```ts +import { createNextServerHelpers } from "@appwrite.io/react/server/next"; + +const helpers = createNextServerHelpers({ + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, +}); + +const session = await helpers.createSessionClient(); + +if (session) { + const user = await session.account.get(); +} +``` + +## Admin client {% #admin-client %} + +For privileged operations (creating users, listing sessions, managing teams) import `createAdminClient` from the framework-specific server entrypoint. It returns a `node-appwrite` client authenticated with your API key. + +```ts +import { createAdminClient } from "@appwrite.io/react/server/next"; + +const admin = createAdminClient({ + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, + apiKey: process.env.APPWRITE_API_KEY!, +}); + +const users = await admin.account.list(); +``` + +Never import any `@appwrite.io/react/server/*` module from client code: the entrypoints are marked `server-only` and will throw if bundled into the browser. + +# Handler routes {% #handlers %} + +The SSR mutations (`sign-in`, `sign-up`, `sign-out`, `oauth/callback`, `oauth/failure`) live behind a handler route you mount once per app. The handler reads the request, talks to Appwrite with the server API key, and writes the session cookie back. + +## Next.js {% #handler-next %} + +```ts +// app/api/appwrite/[...appwrite]/route.ts +import { createAppwriteHandlers } from "@appwrite.io/react/handlers/next"; + +export const { GET, POST } = createAppwriteHandlers({ + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: "/api/appwrite", +}); +``` + +## TanStack Start {% #handler-tanstack %} + +```ts +// src/routes/api/appwrite/$.ts +import { createFileRoute } from "@tanstack/react-router"; +import { createAppwriteHandlers } from "@appwrite.io/react/handlers/tanstack"; + +export const Route = createFileRoute("/api/appwrite/$")({ + server: { + handlers: createAppwriteHandlers({ + endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT, + projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: "/api/appwrite", + }), + }, +}); +``` + +## Cookie and redirect options {% #handler-options %} + +Customize how sessions persist in the browser and where users land after an OAuth round trip. + +```ts +createAppwriteHandlers({ + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: "/api/appwrite", + cookieName: "my-app-session", + cookieOptions: { + sameSite: "strict", + domain: ".example.com", + }, + redirects: { + success: "/dashboard", + failure: "/login?error=oauth", + }, +}); +``` + +| Option | Default | Description | +| --- | --- | --- | +| `cookieName` | `appwrite-session-` | Name of the HTTP-only session cookie. | +| `cookieOptions.secure` | `true` | Set the `Secure` flag on the cookie. Modern browsers accept this on `localhost` over HTTP. | +| `cookieOptions.sameSite` | `"lax"` | SameSite policy. | +| `cookieOptions.httpOnly` | `true` | HttpOnly flag. | +| `cookieOptions.path` | `"/"` | Cookie path. | +| `cookieOptions.domain` | unset | Restrict cookie to a domain. | +| `redirects.success` | `"/"` | URL to redirect to after a successful OAuth login. | +| `redirects.failure` | `"/"` | URL to redirect to after a failed OAuth attempt. | + +# Required scopes {% #scopes %} + +Your server API key needs three scopes for the SSR handler to function: + +- `users.write` (sign-up creates a user) +- `users.read` (sign-up confirms the created user) +- `sessions.write` (sign-in, sign-out, and OAuth callbacks create or delete sessions) + +# Next steps {% #next-steps %} + +- [Start with React](/docs/quick-starts/react) +- [Start with Next.js](/docs/quick-starts/nextjs) +- [Start with TanStack Start](/docs/quick-starts/tanstack-start) +- [Account API reference](/docs/references/cloud/client-web/account) diff --git a/src/routes/docs/quick-starts/nextjs/+page.markdoc b/src/routes/docs/quick-starts/nextjs/+page.markdoc index 3487e84f12f..cad7ea61b8c 100644 --- a/src/routes/docs/quick-starts/nextjs/+page.markdoc +++ b/src/routes/docs/quick-starts/nextjs/+page.markdoc @@ -1,12 +1,14 @@ --- layout: article title: Start with Next.js -description: Learn how to use Appwrite to add authentication, user management, file storage, and more to your Next.js apps. +description: Build Next.js apps with the Appwrite React library. Add server-rendered authentication, sign-in, sign-up, and user state without writing the SSR plumbing yourself. difficulty: beginner -readtime: 3 +readtime: 7 back: /docs/quick-starts --- -Learn how to setup your first Next.js project powered by Appwrite. + +Learn how to set up your first Next.js project with the [Appwrite React library](/docs/products/auth/react). The library ships SSR auth handlers, server helpers, and the same React hooks you use on the client. + {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). @@ -33,99 +35,178 @@ Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `l You can skip optional steps. {% /section %} -{% section #step-2 step=2 title="Create Next.js project" %} -Create a Next.js project by running the following command: + +{% section #step-2 step=2 title="Create an API key" %} + +In your project, go to **Overview** > **Integrations** > **API keys** and create a new key with the scopes `users.read`, `users.write`, and `sessions.write`. Copy the key secret. The SSR handler uses this to create sessions on behalf of users; never expose it to the browser. + +{% /section %} + +{% section #step-3 step=3 title="Create Next.js project" %} + +Create a Next.js project with TypeScript and the App Router. ```sh -npx create-next-app@latest && cd my-app +npx create-next-app@latest my-app --ts --app && cd my-app ``` -When prompted, configure your project with these recommended settings: -- **Would you like to use TypeScript?** → No -- **Would you like to use ESLint?** → Yes -- **Would you like to use Tailwind CSS?** → No (unless you plan to use it) -- **Would you like to use `src/` directory?** → Yes/No (either works for this tutorial) -- **Would you like to use App Router?** → Yes -- **Would you like to customize the default import alias?** → No +Accept the defaults for the remaining prompts. -These settings will create a minimal Next.js setup that's perfect for getting started with Appwrite. {% /section %} -{% section #step-3 step=3 title="Install Appwrite SDK" %} -Install the JavaScript Appwrite SDK. +{% section #step-4 step=4 title="Install the React library" %} + +Install the React library along with the Appwrite Web SDK, Appwrite Node SDK, and `@tanstack/react-query` packages. ```sh -npm install appwrite +npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query ``` + {% /section %} -{% section #step-4 step=4 title="Define Appwrite service" %} -Find your project's ID in the **Settings** page. -{% only_dark %} -![Project settings screen](/images/docs/quick-starts/dark/project-id.avif) -{% /only_dark %} -{% only_light %} -![Project settings screen](/images/docs/quick-starts/project-id.avif) -{% /only_light %} +{% section #step-5 step=5 title="Configure environment variables" %} + +Create a `.env.local` file at the project root. Replace ``, ``, and `` with your own values. + +```sh +NEXT_PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 +NEXT_PUBLIC_APPWRITE_PROJECT_ID= +APPWRITE_API_KEY= +``` + +`NEXT_PUBLIC_*` values are shipped to the browser. `APPWRITE_API_KEY` stays server-only. + +{% /section %} + +{% section #step-6 step=6 title="Mount the auth handler route" %} + +Create `app/api/appwrite/[...appwrite]/route.ts`. The handler exposes the `sign-in`, `sign-up`, `sign-out`, and `oauth/callback` endpoints that the React hooks POST to in SSR mode. + +```ts +import { createAppwriteHandlers } from "@appwrite.io/react/handlers/next"; + +export const { GET, POST } = createAppwriteHandlers({ + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: "/api/appwrite", +}); +``` + +{% /section %} + +{% section #step-7 step=7 title="Wrap your app with AppwriteProvider" %} + +Create `app/providers.tsx`. The provider runs in client components and accepts the SSR session secret as a prop so the underlying Web SDK can hydrate authenticated. + +```tsx +"use client"; + +import { AppwriteProvider } from "@appwrite.io/react"; + +export function Providers({ + session, + children, +}: { + session?: string | null; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} +``` -Create a new file `app/appwrite.js` and add the following code to it, replace `` with your project ID. +Replace `app/layout.tsx` with the following. It reads the session cookie on the server and passes it into the provider. -```client-web -import { Client, Account } from 'appwrite'; +```tsx +import { createNextServerHelpers } from "@appwrite.io/react/server/next"; +import { Providers } from "./providers"; -export const client = new Client(); +const appwrite = { + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, +}; -client - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); // Replace with your project ID +export default async function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + const helpers = createNextServerHelpers(appwrite); + const session = await helpers.readSessionCookie(); -export const account = new Account(client); -export { ID } from 'appwrite'; + return ( + + + {children} + + + ); +} +``` + +{% /section %} + +{% section #step-8 step=8 title="Read the user on the server" %} + +Replace `app/page.tsx`. `getLoggedInUser()` calls the Appwrite API server-side with the session cookie, so the user is rendered with the first byte. + +```tsx +import { createNextServerHelpers } from "@appwrite.io/react/server/next"; +import { AuthPanel } from "./auth-panel"; + +const appwrite = { + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, +}; + +export default async function Page() { + const helpers = createNextServerHelpers(appwrite); + const user = await helpers.getLoggedInUser(); + + return ( +
+

Appwrite React library on Next.js

+

SSR user: {user?.email ?? "signed out"}

+ +
+ ); +} ``` + {% /section %} -{% section #step-5 step=5 title="Create a login page" %} -Create or update `app/page.js` file and add the following code to it. -```js +{% section #step-9 step=9 title="Add the client auth panel" %} + +Create `app/auth-panel.tsx`. The hooks POST to the handler route, the server sets a cookie, and `router.refresh()` re-runs the server component so the SSR user updates. + +```tsx "use client"; + import { useState } from "react"; -import { account, ID } from "./appwrite"; +import { useAuth } from "@appwrite.io/react"; +import { useRouter } from "next/navigation"; -const LoginPage = () => { - const [loggedInUser, setLoggedInUser] = useState(null); +export function AuthPanel() { + const { user, isLoading, signIn, signUp, signOut, error } = useAuth(); + const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); - const login = async (email, password) => { - const session = await account.createEmailPasswordSession({ - email, - password - }); - setLoggedInUser(await account.get()); - }; - - const register = async () => { - await account.create({ - userId: ID.unique(), - email, - password, - name - }); - login(email, password); - }; - - const logout = async () => { - await account.deleteSession({ sessionId: 'current' }); - setLoggedInUser(null); - }; - - if (loggedInUser) { + if (isLoading) return

Loading...

; + + if (user) { return (
-

Logged in as {loggedInUser.name}

-
); @@ -133,43 +214,57 @@ const LoginPage = () => { return (
-

Not logged in

-
- setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - setName(e.target.value)} - /> - - -
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} + /> + + + {error &&

{error.message}

}
); -}; - -export default LoginPage; +} ``` + {% /section %} -{% section #step-6 step=6 title="All set" %} -Run your project with `npm run dev` and open [Localhost on Port 3000](http://localhost:3000) in your browser. +{% section #step-10 step=10 title="Run your app" %} + +```sh +npm run dev +``` + +Open [localhost on port 3000](http://localhost:3000). Sign up, sign out, and sign back in to verify the cookie-based SSR flow. -Don't forget to add some CSS to suit your style. {% /section %} + +# Next steps {% #next-steps %} + +For server-side admin operations, per-request session clients, OAuth callbacks, and the full hook reference, see the [React library docs](/docs/products/auth/react). diff --git a/src/routes/docs/quick-starts/nextjs/prompt.md b/src/routes/docs/quick-starts/nextjs/prompt.md index 2e159d12260..689a894c855 100644 --- a/src/routes/docs/quick-starts/nextjs/prompt.md +++ b/src/routes/docs/quick-starts/nextjs/prompt.md @@ -1,80 +1,213 @@ ## Add Appwrite Auth to a New Next.js App -Add Appwrite auth to a new Next.js app (**App Router**), with a working login/register/logout page. +Add Appwrite auth to a new Next.js app (**App Router**) using the official Appwrite React library, with a working sign-up, sign-in, and sign-out flow backed by SSR session cookies. - Do exactly these steps in order. Confirm each step succeeds before continuing. If any command fails, show the error and fix it automatically. - Respect the user's package manager at all times. Do not use NPM if the user uses something else. -## Step 1: Create or Use Existing Next.js App +## Step 1: Create or use existing Next.js app - First, check if the current working directory contains files that appear unrelated to a development workspace (e.g., personal files, downloads, random documents, media files). If so, ask the user: "This directory contains files that don't look like a development project. Would you like to proceed here anyway, or create a subdirectory with a specific folder name?" - If the directory is empty OR contains an existing project (`package.json`, source code, config files, etc.), proceed with integration without asking. - Create the project in the current working directory (`.`) - do NOT use `cd` to switch directories. - If you already have a Next.js project open, stay in it and integrate Appwrite into it (**App Router** required). -- Otherwise, run: `npx create-next-app@latest . --eslint` -- When prompted: **TypeScript** = No, **ESLint** = Yes, **Tailwind** = No, **src dir** = your choice, **App Router** = Yes, **Import alias** = No. +- Otherwise, run: `npx create-next-app@latest . --ts --app` +- Accept the defaults for the remaining prompts. -## Step 2: Install Appwrite SDK +## Step 2: Install the Appwrite React library -- Run: `npm install appwrite` +- Run: `npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query` -## Step 3: Create Appwrite Client Module +## Step 3: Configure environment variables _Ask the user for details; never assume._ - Ask the user for: - **Appwrite Cloud Region** (e.g. `fra`, `nyc`) - **Project ID** (from Console -> Settings) -- If the user doesn't know, guide them to the **Appwrite Console** to copy these. Do not attempt to infer or access their project. -- Hardcode the endpoint and project ID in the file `app/appwrite.js` (or `app/appwrite.ts` if TS) if provided, else leave a placeholder and ask the user to provide them. -- Create file `app/appwrite.js` (or `app/appwrite.ts` if TS) with key snippet: - -```js -import { Client, Account } from 'appwrite'; -const endpoint = ''; -const projectId = ''; -if (!endpoint || !projectId) throw new Error('Missing Appwrite endpoint and project ID'); -export const client = new Client().setEndpoint(endpoint).setProject(projectId); -export const account = new Account(client); -export { ID } from 'appwrite'; + - **API key** with scopes `users.read`, `users.write`, `sessions.write` (Console -> Overview -> Integrations -> API keys) +- Create a `.env.local` file at the project root: + +```sh +NEXT_PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 +NEXT_PUBLIC_APPWRITE_PROJECT_ID= +APPWRITE_API_KEY= +``` + +- The `APPWRITE_API_KEY` is server-only. Never expose it to the browser. + +## Step 4: Mount the auth handler route + +- Create `app/api/appwrite/[...appwrite]/route.ts` so the library's sign-in, sign-up, sign-out, and OAuth callback endpoints are reachable: + +```ts +import { createAppwriteHandlers } from '@appwrite.io/react/handlers/next'; + +export const { GET, POST } = createAppwriteHandlers({ + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: '/api/appwrite' +}); +``` + +## Step 5: Wrap the app with AppwriteProvider + +- Create `app/providers.tsx`: + +```tsx +'use client'; + +import { AppwriteProvider } from '@appwrite.io/react'; + +export function Providers({ + session, + children +}: { + session?: string | null; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} +``` + +- Replace `app/layout.tsx` to read the session cookie server-side and pass it into the provider: + +```tsx +import { createNextServerHelpers } from '@appwrite.io/react/server/next'; +import { Providers } from './providers'; + +const appwrite = { + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID! +}; + +export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + const helpers = createNextServerHelpers(appwrite); + const session = await helpers.readSessionCookie(); + + return ( + + + {children} + + + ); +} ``` -## Step 4: Build the Login Page (Client Component) +## Step 6: Build the auth page + +- If this is a fresh project you just created, replace `app/page.tsx` to read the user server-side and render the auth panel. If you are working in an existing project, create a new route (e.g. `app/auth/page.tsx`) instead of overriding the default route. + +```tsx +import { createNextServerHelpers } from '@appwrite.io/react/server/next'; +import { AuthPanel } from './auth-panel'; + +const appwrite = { + endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!, + projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID! +}; + +export default async function Page() { + const helpers = createNextServerHelpers(appwrite); + const user = await helpers.getLoggedInUser(); -- If this is a fresh project you just created above, you may replace `app/page.js` with this component using `"use client"`. -- If you are working in an existing project, create a new route `app/auth/page.js` (or `.tsx`) instead of overriding the default route. -- It must render: - - Email/password inputs - - Name input for registration - - Buttons: **Login**, **Register**, **Logout** - - Shows "Logged in as \" when a session exists -- Implement functions: - - `login(email, password)`: `account.createEmailPasswordSession({ email, password })` then set user via `account.get()` - - `register()`: `account.create({ userId: ID.unique(), email, password, name })` then call `login` - - `logout()`: `account.deleteSession({ sessionId: 'current' })` then clear user state + return ( +
+

SSR user: {user?.email ?? 'signed out'}

+ +
+ ); +} +``` + +- Create `app/auth-panel.tsx` for the client-side hook usage: + +```tsx +'use client'; + +import { useState } from 'react'; +import { useAuth } from '@appwrite.io/react'; +import { useRouter } from 'next/navigation'; + +export function AuthPanel() { + const { user, isLoading, signIn, signUp, signOut, error } = useAuth(); + const router = useRouter(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [name, setName] = useState(''); + + if (isLoading) return

Loading...

; + + if (user) { + return ( + + ); + } + + return ( +
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} + /> + + + {error &&

{error.message}

} +
+ ); +} +``` -## Step 5: Verify Environment +## Step 7: Verify environment _Ask the user to confirm._ -- Confirm with the user that the endpoint and project ID are hardcoded in the file `app/appwrite.js` (or `app/appwrite.ts` if TS). +- Confirm `.env.local` has the endpoint, project ID, and API key set. - Ensure the **Web** app platform exists in **Appwrite Console** with **Hostname** = `localhost`. If missing, guide the user to add it. -## Step 6: Run and Test +## Step 8: Run and test - Run: `npm run dev` - Open: `http://localhost:3000` - Test flows: - - Register a new user and auto login works - - Logout then login again -- Surface any Appwrite errors (invalid project, endpoint, CORS/hostname) and fix by guiding updates to `appwrite.js` and **Console** settings. - -## Step 7: Optional Hardening - -- If the user wants TypeScript, create `app/appwrite.ts` and `app/page.tsx` with proper types. -- Add minimal styling if requested; functionality first. + - Sign up a new user, confirm the SSR-rendered user reflects the change after `router.refresh()` + - Sign out, then sign in again +- Surface any Appwrite errors (invalid project, endpoint, CORS/hostname, missing key scopes) and fix by guiding updates to `.env.local` and Console settings. ## Deliverables -- A running Next.js app with working Appwrite auth (register/login/logout) -- Files created/updated: `package.json` (deps), `app/appwrite.js`, `app/page.js` +- A running Next.js app with working Appwrite auth using `@appwrite.io/react` +- Files created/updated: `package.json` (deps), `.env.local`, `app/api/appwrite/[...appwrite]/route.ts`, `app/providers.tsx`, `app/layout.tsx`, `app/page.tsx`, `app/auth-panel.tsx` diff --git a/src/routes/docs/quick-starts/react/+page.markdoc b/src/routes/docs/quick-starts/react/+page.markdoc index f416f6981a5..da8e8fd8eec 100644 --- a/src/routes/docs/quick-starts/react/+page.markdoc +++ b/src/routes/docs/quick-starts/react/+page.markdoc @@ -1,13 +1,14 @@ --- layout: article title: Start with React -description: Build React apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more. +description: Build React apps with the Appwrite React library and add authentication, sign-in, sign-up, and user state in a few lines. difficulty: beginner -readtime: 3 +readtime: 5 back: /docs/quick-starts --- -Learn how to setup your first React project powered by Appwrite. +Learn how to set up your first React project with the [Appwrite React library](/docs/products/auth/react). + {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). @@ -34,22 +35,26 @@ Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `l You can skip optional steps. {% /section %} + {% section #step-2 step=2 title="Create React project" %} Create a Vite project. ```sh -npm create vite@latest my-app -- --template react && cd my-app +npm create vite@latest my-app -- --template react-ts && cd my-app +npm install ``` {% /section %} -{% section #step-3 step=3 title="Install Appwrite" %} -Install the JavaScript Appwrite SDK. +{% section #step-3 step=3 title="Install the React library" %} + +Install the Appwrite React library along with the `appwrite` Web SDK and `@tanstack/react-query` peer dependency. ```sh -npm install appwrite +npm install @appwrite.io/react appwrite @tanstack/react-query ``` {% /section %} -{% section #step-4 step=4 title="Import Appwrite" %} + +{% section #step-4 step=4 title="Configure environment variables" %} Find your project's ID in the **Settings** page. {% only_dark %} @@ -58,92 +63,109 @@ Find your project's ID in the **Settings** page. {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.avif) {% /only_light %} -Create a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID. - -```client-web -import { Client, Account} from 'appwrite'; -export const client = new Client(); +Create a `.env` file at the project root and add your endpoint and project ID. Replace `` and `` with your own values. -client - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); // Replace with your project ID +```sh +VITE_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 +VITE_APPWRITE_PROJECT_ID= +``` +{% /section %} -export const account = new Account(client); -export { ID } from 'appwrite'; +{% section #step-5 step=5 title="Wrap your app with AppwriteProvider" %} + +Replace the contents of `src/main.tsx` with the following. + +```tsx +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { AppwriteProvider } from "@appwrite.io/react"; +import App from "./App"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + + + + + , +); ``` + +`AppwriteProvider` sets up the Appwrite Web SDK client and a per-instance TanStack Query cache so all hooks share the same auth state. + {% /section %} -{% section #step-5 step=5 title="Create a login page" %} -Add the following code to `src/App.jsx`. - -```js -import React, { useState } from 'react'; -import { account, ID } from './lib/appwrite'; - -const App = () => { - const [loggedInUser, setLoggedInUser] = useState(null); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [name, setName] = useState(''); - - async function login(email, password) { - await account.createEmailPasswordSession({ - email, - password - }); - setLoggedInUser(await account.get()); + +{% section #step-6 step=6 title="Add sign-up, sign-in, and sign-out" %} + +Replace the contents of `src/App.tsx` with the following. + +```tsx +import { useState } from "react"; +import { useAuth } from "@appwrite.io/react"; + +export default function App() { + const { user, isLoading, signIn, signUp, signOut, error } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [name, setName] = useState(""); + + if (isLoading) return

Loading...

; + + if (user) { + return ( +
+

Welcome, {user.name || user.email}

+ +
+ ); } return ( -
-

- {loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'} -

- -
- setEmail(e.target.value)} /> - setPassword(e.target.value)} /> - setName(e.target.value)} /> - - - - - - -
-
+
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} + /> + + + {error &&

{error.message}

} +
); -}; +} +``` + +`useAuth` returns the current user, loading state, and the `signIn`, `signUp`, and `signOut` mutations. The user state is cached by TanStack Query and stays in sync across components automatically. -export default App; +{% /section %} + +{% section #step-7 step=7 title="Run your app" %} +Run your project. + +```sh +npm run dev ``` + +Open [localhost on port 5173](http://localhost:5173) in your browser. Sign up, sign out, and sign back in to verify the flow. {% /section %} -{% section #step-6 step=6 title="All set" %} -Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. -{% /section %} \ No newline at end of file +# Next steps {% #next-steps %} + +For Next.js or TanStack Start integration, server-side rendering, and the full hook reference, see the [React library docs](/docs/products/auth/react). diff --git a/src/routes/docs/quick-starts/react/prompt.md b/src/routes/docs/quick-starts/react/prompt.md index c2f180be592..99798ab284c 100644 --- a/src/routes/docs/quick-starts/react/prompt.md +++ b/src/routes/docs/quick-starts/react/prompt.md @@ -1,6 +1,6 @@ ## Add Appwrite Auth to a New React (Vite) App -Goal: Add Appwrite auth to a new React (Vite) app with a working login/register/logout page. +Goal: Add Appwrite auth to a new React (Vite) app using the official Appwrite React library, with a working sign-up, sign-in, and sign-out flow. Do exactly these steps in order. Confirm each step succeeds before continuing. If any command fails, show the error and fix it automatically. @@ -11,60 +11,112 @@ Respect user's package manager at all time. Don't use NPM if the user uses somet - First, check if the current working directory contains files that appear unrelated to a development workspace (e.g., personal files, downloads, random documents, media files). - If unrelated files are detected, ask the user: 'The current directory appears to contain personal or non-project files. Would you like to: (1) proceed here anyway, or (2) create the project in a subdirectory with a specific folder name?' and proceed based on their choice. - If the directory is empty OR contains an existing project (`package.json`, source files, config files, etc.), proceed without asking - integrate Appwrite into whatever is there. -- For a new project, run: `npm create vite@latest . -- --template react` +- For a new project, run: `npm create vite@latest . -- --template react-ts` - Create the project in the current directory (`.`). Do NOT use `cd` to switch directories. -## Step 2: Install Appwrite SDK +## Step 2: Install the Appwrite React library -- Run: `npm install appwrite` +- Run: `npm install @appwrite.io/react appwrite @tanstack/react-query` -## Step 3: Create Appwrite Client Module (Ask User for Details; Never Assume) +## Step 3: Configure environment variables (Ask User for Details; Never Assume) - Ask the user for: - Appwrite Cloud Region (e.g. `fra`, `nyc`) - **Project ID** (from Console -> Settings) -- Hardcode the endpoint and project ID in the file: `src/lib/appwrite.js` (or `.ts`) if provided, else leave placeholder and ask the user to provide them. -- Create file: `src/lib/appwrite.js` (or `.ts`) with key snippet: - -```js -import { Client, Account, ID } from 'appwrite'; -const endpoint = 'https://.cloud.appwrite.io/v1'; -const projectId = ''; -if (!endpoint || !projectId) throw new Error('Missing Appwrite endpoint and project ID'); -const client = new Client().setEndpoint(endpoint).setProject(projectId); -export const account = new Account(client); -export { ID }; +- Create a `.env` file at the project root with the values provided. If either is missing, leave a placeholder and ask the user to fill it in: + +```sh +VITE_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 +VITE_APPWRITE_PROJECT_ID= ``` -## Step 4: Build the Login Page +## Step 4: Mount AppwriteProvider + +- Replace `src/main.tsx` (or `.jsx`) so the entire app is wrapped with `AppwriteProvider`: + +```tsx +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { AppwriteProvider } from '@appwrite.io/react'; +import App from './App'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + + + +); +``` -- If this is a fresh project, you may replace `src/App.jsx` (or `.tsx`) with a component that renders the auth UI. -- If you are working in an existing project, add a new route/page instead of overriding the default route. If routing is not set up, install `react-router-dom` and add a `/auth` route that renders this component. -- The component should render: - - Email/password inputs - - Name input for registration - - Buttons: **Login**, **Register**, **Logout** - - Shows "Logged in as \" when a session exists -- Implement functions: - - `login(email, password)`: `account.createEmailPasswordSession({ email, password })` then set user via `account.get()` - - `register()`: `account.create({ userId: ID.unique(), email, password, name })` then call `login` - - `logout()`: `account.deleteSession({ sessionId: 'current' })` then clear user state +## Step 5: Build the auth page + +- If this is a fresh project, you may replace `src/App.tsx` (or `.jsx`) with a component that renders the auth UI. +- If you are working in an existing project, add a new route/page instead of overriding the default route. If routing is not set up, install `react-router-dom` and add an `/auth` route that renders this component. +- The component must render: + - Email, password, and name inputs + - Buttons: **Sign up**, **Sign in**, **Sign out** + - Shows "Welcome, \" when a session exists +- Use the `useAuth` hook from `@appwrite.io/react`: + +```tsx +import { useState } from 'react'; +import { useAuth } from '@appwrite.io/react'; + +export default function App() { + const { user, isLoading, signIn, signUp, signOut, error } = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [name, setName] = useState(''); + + if (isLoading) return

Loading...

; + + if (user) { + return ( +
+

Welcome, {user.name || user.email}

+ +
+ ); + } + + return ( +
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} + /> + + + {error &&

{error.message}

} +
+ ); +} +``` -## Step 5: Verify Environment (Ask User to Confirm) +## Step 6: Verify environment (Ask User to Confirm) -- Confirm endpoint and project ID are set in `src/lib/appwrite.js`. +- Confirm the `.env` file contains the correct endpoint and project ID. - Ensure the Web app platform exists in Appwrite Console with **Hostname** = `localhost`. If missing, guide the user to add it. -## Step 6: Run and Test +## Step 7: Run and test -- Run: `npm run dev -- --open --port 3000` -- Open: `http://localhost:3000` +- Run: `npm run dev` +- Open: `http://localhost:5173` - Test flows: - - Register a new user and auto login works - - Logout then login again -- Surface any Appwrite errors (invalid project, endpoint, CORS/hostname) and fix by guiding updates to `appwrite.js` and Console settings. + - Sign up a new user and confirm the page shows the welcome state + - Sign out, then sign in again +- Surface any Appwrite errors (invalid project, endpoint, CORS/hostname) and fix by guiding updates to `.env` and Console settings. ## Deliverables -- A running React app with working Appwrite auth (register/login/logout) -- Files created/updated: `package.json` (deps), `src/lib/appwrite.js`, `src/App.jsx` +- A running React app with working Appwrite auth using `@appwrite.io/react` +- Files created/updated: `package.json` (deps), `.env`, `src/main.tsx`, `src/App.tsx` diff --git a/src/routes/docs/quick-starts/tanstack-start/+page.markdoc b/src/routes/docs/quick-starts/tanstack-start/+page.markdoc index 780ec427580..46f284a7907 100644 --- a/src/routes/docs/quick-starts/tanstack-start/+page.markdoc +++ b/src/routes/docs/quick-starts/tanstack-start/+page.markdoc @@ -1,12 +1,14 @@ --- layout: article title: Start with TanStack Start -description: Learn how to use Appwrite to add authentication, user management, file storage, and more to your TanStack Start apps. +description: Build TanStack Start apps with the Appwrite React library. Add server-rendered authentication via file-route handlers and server functions. difficulty: beginner -readtime: 3 +readtime: 7 back: /docs/quick-starts --- -Learn how to setup your first TanStack Start project powered by Appwrite. + +Learn how to set up your first TanStack Start project with the [Appwrite React library](/docs/products/auth/react). The library exposes a TanStack file-route handler, server helpers, and the same React hooks you use on the client. + {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). @@ -33,133 +35,193 @@ Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `l You can skip optional steps. {% /section %} -{% section #step-2 step=2 title="Create TanStack Start project" %} + +{% section #step-2 step=2 title="Create an API key" %} + +In your project, go to **Overview** > **Integrations** > **API keys** and create a new key with the scopes `users.read`, `users.write`, and `sessions.write`. Copy the key secret. The SSR handler uses this to create sessions on behalf of users; never expose it to the browser. + +{% /section %} + +{% section #step-3 step=3 title="Create TanStack Start project" %} + Create a TanStack Start project. ```sh -npm create @tanstack/start@latest my-app && cd my-app +npx @tanstack/cli create my-app --framework react && cd my-app ``` + {% /section %} -{% section #step-3 step=3 title="Install Appwrite" %} -Install the JavaScript Appwrite SDK. +{% section #step-4 step=4 title="Install the React library" %} + +Install the React library along with the Appwrite Web SDK, Appwrite Node SDK, and `@tanstack/react-query` packages. ```sh -npm install appwrite +npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query ``` + {% /section %} -{% section #step-4 step=4 title="Import Appwrite" %} -Find your project's ID in the **Settings** page. -{% only_dark %} -![Project settings screen](/images/docs/quick-starts/dark/project-id.avif) -{% /only_dark %} -{% only_light %} -![Project settings screen](/images/docs/quick-starts/project-id.avif) -{% /only_light %} -Create a new file `src/utils/appwrite.ts` and add the following code to it, replace `` with your project ID. +{% section #step-5 step=5 title="Configure environment variables" %} + +Create a `.env` file at the project root. Replace ``, ``, and `` with your own values. + +```sh +VITE_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 +VITE_APPWRITE_PROJECT_ID= +APPWRITE_API_KEY= +``` + +`VITE_*` values are shipped to the browser. `APPWRITE_API_KEY` stays server-only. + +{% /section %} -```client-web -import { Client, Account, ID, Models } from 'appwrite'; +{% section #step-6 step=6 title="Mount the auth handler route" %} -export const client = new Client(); +Create `src/routes/api/appwrite/$.ts`. The TanStack file route exposes the library's `sign-in`, `sign-up`, `sign-out`, and `oauth/callback` endpoints under `/api/appwrite/*`. -client - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject(''); // Replace with your project ID +```ts +import { createFileRoute } from "@tanstack/react-router"; +import { createAppwriteHandlers } from "@appwrite.io/react/handlers/tanstack"; -export const account = new Account(client); -export { ID }; -export type { Models }; +export const Route = createFileRoute("/api/appwrite/$")({ + server: { + handlers: createAppwriteHandlers({ + endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT, + projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: "/api/appwrite", + }), + }, +}); ``` + {% /section %} -{% section #step-5 step=5 title="Create a login page" %} -Create or update `src/routes/index.tsx` with the following code. + +{% section #step-7 step=7 title="Read auth state in a server function" %} + +Replace `src/routes/index.tsx` with the following. A `createServerFn` loader reads the session cookie server-side; the page receives the result via `Route.useLoaderData()` and passes it into `AppwriteProvider`. ```tsx -import { useState } from 'react'; -import { createFileRoute } from '@tanstack/react-router'; -import { account, ID, type Models } from '../utils/appwrite'; +import { useState } from "react"; +import { createFileRoute, useRouter } from "@tanstack/react-router"; +import { createServerFn } from "@tanstack/react-start"; +import { AppwriteProvider, useAuth } from "@appwrite.io/react"; +import { createTanStackServerHelpers } from "@appwrite.io/react/server/tanstack"; + +const getAuthSnapshot = createServerFn({ method: "GET" }).handler(async () => { + const helpers = createTanStackServerHelpers({ + endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT, + projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID, + }); + return { + session: helpers.readSessionCookie() ?? null, + user: await helpers.getLoggedInUser(), + }; +}); -export const Route = createFileRoute('/')({ - component: Index, +export const Route = createFileRoute("/")({ + loader: () => getAuthSnapshot(), + component: Page, }); -function Index() { - const [loggedInUser, setLoggedInUser] = useState | null>(null); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [name, setName] = useState(''); +function Page() { + const { session, user } = Route.useLoaderData(); + + return ( + +
+

Appwrite React library on TanStack Start

+

SSR user: {user?.email ?? "signed out"}

+ +
+
+ ); +} - async function login(email: string, password: string) { - await account.createEmailPasswordSession({ - email, - password, - }); - setLoggedInUser(await account.get()); - } +function AuthPanel() { + const { user, isLoading, signIn, signUp, signOut, error } = useAuth(); + const router = useRouter(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [name, setName] = useState(""); - async function register() { - await account.create({ - userId: ID.unique(), - email, - password, - name, - }); - await login(email, password); - } - - async function logout() { - await account.deleteSession({ sessionId: 'current' }); - setLoggedInUser(null); - } - - if (loggedInUser) { - return ( -
-

Logged in as {loggedInUser.name}

- -
- ); - } + if (isLoading) return

Loading...

; + if (user) { return ( -
-

Not logged in

-
- setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - setName(e.target.value)} - /> - - -
-
+
+

Welcome, {user.name || user.email}

+ +
); + } + + return ( +
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} + /> + + + {error &&

{error.message}

} +
+ ); } ``` + +After every auth mutation, `router.invalidate()` re-runs the loader so the SSR user reflects the new session cookie. + +{% info title="Multi-route apps" %} +In a multi-route TanStack Start app, move `AppwriteProvider` (and the auth loader) into `src/routes/__root.tsx` so the TanStack Query cache and auth state persist across navigations. Mounting the provider in a single route works for this quickstart, but resets the cache every time the route changes. +{% /info %} + {% /section %} -{% section #step-6 step=6 title="All set" %} -Run your project with `npm run dev` and open [localhost on port 3000](http://localhost:3000) in your browser. +{% section #step-8 step=8 title="Run your app" %} + +```sh +npm run dev +``` + +Open [localhost on port 3000](http://localhost:3000). Sign up, sign out, and sign back in to verify the cookie-based SSR flow. + {% /section %} + +# Next steps {% #next-steps %} + +For server-side admin operations, per-request session clients, OAuth callbacks, and the full hook reference, see the [React library docs](/docs/products/auth/react). diff --git a/src/routes/docs/quick-starts/tanstack-start/prompt.md b/src/routes/docs/quick-starts/tanstack-start/prompt.md index 1292b29b194..557807504be 100644 --- a/src/routes/docs/quick-starts/tanstack-start/prompt.md +++ b/src/routes/docs/quick-starts/tanstack-start/prompt.md @@ -1,4 +1,6 @@ -## Add Appwrite auth to a TanStack Start app with a working login/register/logout page +## Add Appwrite Auth to a TanStack Start app + +Add Appwrite auth to a new TanStack Start app using the official Appwrite React library, with a working sign-up, sign-in, and sign-out flow backed by SSR session cookies. Do exactly these steps in order. Confirm each step succeeds before continuing. If any command fails, show the error and fix it automatically. @@ -9,126 +11,162 @@ Respect the user's package manager at all times. Don't use NPM if the user uses - First, check what files exist in the current working directory. - If the directory contains files that appear unrelated to a development workspace (e.g., personal documents, downloads, photos, random files that don't belong in a code project), ask the user: "The current directory contains files that don't appear to be part of a development project. Would you like to proceed here anyway, or create a subdirectory with a specific folder name?" - If the directory is empty OR contains an existing project (`package.json`, config files, `src` folder, etc.), proceed with integration without asking - just work with what's there. -- To scaffold a new TanStack Start project, run: `npm create @tanstack/start@latest .` (use `.` to create in the current directory - do NOT use `cd` to switch directories) +- To scaffold a new TanStack Start project, run: `npx @tanstack/cli create . --framework react` (use `.` to create in the current directory - do NOT use `cd` to switch directories) + +## Step 2: Install the Appwrite React library -## Step 2: Install Appwrite SDK +- Run: `npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query` -- Run: `npm install appwrite` +## Step 3: Configure environment variables -## Step 3: Create Appwrite client module (ask user for details; never assume) +_Ask the user for details; never assume._ - Ask the user for: - **Appwrite Cloud Region** (e.g. `fra`, `nyc`) - **Project ID** (from Console → Settings) -- Hardcode the endpoint and project ID in the file `src/utils/appwrite.ts` (or `.js`) if provided, else leave placeholder and ask the user to provide them. -- Create file `src/utils/appwrite.ts` (or `.js`) with key snippet: + - **API key** with scopes `users.read`, `users.write`, `sessions.write` (Console → Overview → Integrations → API keys) +- Create a `.env` file at the project root: + +```sh +VITE_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 +VITE_APPWRITE_PROJECT_ID= +APPWRITE_API_KEY= +``` + +- The `APPWRITE_API_KEY` is server-only. Never expose it to the browser. + +## Step 4: Mount the auth handler route + +- Create `src/routes/api/appwrite/$.ts`: ```ts -import { Client, Account, ID, type Models } from 'appwrite'; -const endpoint = 'https://.cloud.appwrite.io/v1'; -const projectId = ''; -if (!endpoint || !projectId) throw new Error('Missing Appwrite endpoint and project ID'); -const client = new Client().setEndpoint(endpoint).setProject(projectId); -export const account = new Account(client); -export { ID }; -export type { Models }; +import { createFileRoute } from '@tanstack/react-router'; +import { createAppwriteHandlers } from '@appwrite.io/react/handlers/tanstack'; + +export const Route = createFileRoute('/api/appwrite/$')({ + server: { + handlers: createAppwriteHandlers({ + endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT, + projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID, + apiKey: process.env.APPWRITE_API_KEY!, + basePath: '/api/appwrite' + }) + } +}); ``` -## Step 4: Build the login route +## Step 5: Build the auth route -- If this is a fresh project, you may replace `src/routes/index.tsx` with an auth UI route. -- If you are working in an existing project, create a new route (e.g., `src/routes/auth.tsx`) instead of overriding the default route. -- The route should render: - - Email/password inputs - - Name input for registration - - Buttons: **Login**, **Register**, **Logout** - - Shows "Logged in as \" when a session exists -- Route scaffold example: +- If this is a fresh project, you may replace `src/routes/index.tsx` with the auth UI route. If you are working in an existing project, create a new route (e.g., `src/routes/auth.tsx`) instead of overriding the default route. +- The route reads SSR auth state via a server function and passes the session into `AppwriteProvider`: ```tsx import { useState } from 'react'; -import { createFileRoute } from '@tanstack/react-router'; -import { account, ID, type Models } from '../utils/appwrite'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { createServerFn } from '@tanstack/react-start'; +import { AppwriteProvider, useAuth } from '@appwrite.io/react'; +import { createTanStackServerHelpers } from '@appwrite.io/react/server/tanstack'; + +const getAuthSnapshot = createServerFn({ method: 'GET' }).handler(async () => { + const helpers = createTanStackServerHelpers({ + endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT, + projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID + }); + return { + session: helpers.readSessionCookie() ?? null, + user: await helpers.getLoggedInUser() + }; +}); export const Route = createFileRoute('/')({ - component: Index + loader: () => getAuthSnapshot(), + component: Page }); -function Index() { - const [user, setUser] = useState | null>(null); +function Page() { + const { session, user } = Route.useLoaderData(); + + return ( + +
+

SSR user: {user?.email ?? 'signed out'}

+ +
+
+ ); +} + +function AuthPanel() { + const { user, isLoading, signIn, signUp, signOut, error } = useAuth(); + const router = useRouter(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); - async function login(e: string, p: string) { - await account.createEmailPasswordSession({ email: e, password: p }); - setUser(await account.get()); - } - - async function register() { - await account.create({ userId: ID.unique(), email, password, name }); - await login(email, password); - } + if (isLoading) return

Loading...

; - async function logout() { - await account.deleteSession({ sessionId: 'current' }); - setUser(null); + if (user) { + return ( + + ); } return (
-

{user ? `Logged in as ${user.name}` : 'Not logged in'}

-
- setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - setName(e.target.value)} - /> - - - {user && ( - - )} -
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + setPassword(e.target.value)} + /> + + + {error &&

{error.message}

}
); } ``` -## Step 5: Verify environment (ask user to confirm) +## Step 6: Verify environment (ask user to confirm) -- Confirm endpoint and project ID are set in `src/utils/appwrite.ts`. +- Confirm `.env` has the endpoint, project ID, and API key set. - Ensure the Web app platform exists in Appwrite Console with **Hostname** = `localhost`. If missing, guide the user to add it. -## Step 6: Run and test +## Step 7: Run and test - Run: `npm run dev` - Open: `http://localhost:3000` - Test flows: - - Register a new user and auto login works - - Logout then login again -- Surface any Appwrite errors (invalid project, endpoint, CORS/hostname) and fix by guiding updates to `appwrite.ts` and Console settings. + - Sign up a new user, confirm the SSR-rendered user reflects the change after `router.invalidate()` + - Sign out, then sign in again +- Surface any Appwrite errors (invalid project, endpoint, CORS/hostname, missing key scopes) and fix by guiding updates to `.env` and Console settings. ## Deliverables -- A TanStack Start app with working Appwrite auth (register/login/logout) -- Files created/updated: `package.json` (deps), `src/utils/appwrite.ts`, auth route file +- A TanStack Start app with working Appwrite auth using `@appwrite.io/react` +- Files created/updated: `package.json` (deps), `.env`, `src/routes/api/appwrite/$.ts`, auth route file diff --git a/src/routes/docs/sdks/+page.markdoc b/src/routes/docs/sdks/+page.markdoc index 5ad3c160477..c8239b4102b 100644 --- a/src/routes/docs/sdks/+page.markdoc +++ b/src/routes/docs/sdks/+page.markdoc @@ -32,6 +32,12 @@ Client libraries for integrating with Appwrite to build client-based application --- * {% only_dark %}{% icon_image src="/images/platforms/dark/react.svg" alt="React logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/light/react.svg" alt="React logo" size="m" /%}{% /only_light %} +* React library `0.1.0` +* [appwrite/sdk-for-react](https://github.com/appwrite/sdk-for-react) +* +--- +* {% only_dark %}{% icon_image src="/images/platforms/dark/react.svg" alt="React logo" size="m" /%}{% /only_dark %} +{% only_light %}{% icon_image src="/images/platforms/light/react.svg" alt="React logo" size="m" /%}{% /only_light %} * React Native SDK `0.25.0` * [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native) * `beta`