Page updated Apr 19, 2024

Vite + React App

This quickstart guide will walk you through how to build a task-list app with TypeScript, Vite, and React. If you are new to these technologies, we recommend you go through the TypeScript, Vite, and official React tutorials first.

Prerequisites

Before you get started, make sure you have the following installed:

You will also need to create an AWS account. Note that AWS Amplify is part of the AWS Free Tier.

Create a project

From your project directory, run the following command:

Terminal
npm create vite@latest react-amplify-gen2 -- --template react-ts

This creates a new React app in a directory called react-amplify-gen2. You can then run the following commands to switch into the new react-amplify-gen2 directory and install the project dependencies:

Terminal
cd react-amplify-gen2
npm install

Create a backend

The easiest way to get started with AWS Amplify is through npm with create-amplify.

Terminal
npm create amplify@latest
Terminal
? Where should we create your project? (.) # press enter

Running this command will scaffold a lightweight Amplify project in your current project with the following files:

1├── amplify/
2│ ├── auth/
3│ │ └── resource.ts
4│ ├── data/
5│ │ └── resource.ts
6│ ├── backend.ts
7│ └── package.json
8├── node_modules/
9├── .gitignore
10├── package-lock.json
11├── package.json
12└── tsconfig.json

Start local dev server

Let's start a local dev server for your app development. For the frontend, run npm run dev to spin up a localhost dev server.

Terminal
npm run dev

Now configure your AWS account to use Amplify. Note: If you already have an AWS profile with credentials on your local machine, and you have configured the corresponding AWS profile with the AmplifyBackendDeployFullAccess permission policy, you can skip this step.

Start Amplify sandbox

For the backend, you can start a cloud sandbox environment. Amplify gives every developer a personal cloud sandbox environment that provides isolated development spaces to rapidly build, test, and iterate. When you're working with a team, each developer will have their own personal cloud sandbox. In a new terminal window, run the following command:

Terminal
npx amplify sandbox
Do not use cloud sandbox environments in production.

You should now have these two commands running concurrently in different terminal windows.

Build a backend

Next, we will add data and auth capabilities to the app. In Amplify Gen 2, the resource.ts files are where you define the corresponding backend resource and details about it.

Add data

create-amplify provides the scaffolding of a amplify/data/resource.ts file, which is ready to deploy.

See the complete amplify/data/resources.ts

Open the amplify/data/resource.ts file in your text editor, and you will see a default data model generated for you.

Note: The steps defined in the code below do not require action to complete this quickstart. These notes are provided for every new app and will help you when you build your next app on your own.

amplify/data/resource.ts
1import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
2
3/*== STEP 1 ===============================================================
4The section below creates a Todo database table with a "content" field. Try
5adding a new "isDone" field as a boolean. The authorization rules below
6specify that owners, authenticated via your Auth resource, can "create",
7"read", "update", and "delete" their own records. Public users,
8authenticated via an API key, can only "read" records.
9=========================================================================*/
10const schema = a.schema({
11 Todo: a
12 .model({
13 content: a.string()
14 })
15 .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])])
16});
17
18export type Schema = ClientSchema<typeof schema>;
19
20export const data = defineData({
21 schema,
22 authorizationModes: {
23 defaultAuthorizationMode: 'apiKey',
24 // API Key is used for allow.publicApiKey() rules
25 apiKeyAuthorizationMode: {
26 expiresInDays: 30
27 }
28 }
29});
30
31/*== STEP 2 ===============================================================
32Go to your frontend source code. From your client-side code, generate a
33Data client to make CRUDL requests to your table. (THIS SNIPPET WILL ONLY
34WORK IN THE FRONTEND CODE FILE.)
35
36Using JavaScript or Next.js React Server Components, Middleware, Server
37Actions, or Pages Router? Review how to generate Data clients for those use
38cases: https://docs.amplify.aws/gen2/build-a-backend/data/connect-to-API/
39=========================================================================*/
40
41/*
42"use client"
43import { generateClient } from "aws-amplify/data";
44import { type Schema } from "@/amplify/data/resource";
45
46const client = generateClient<Schema>() // use this Data client for CRUDL requests
47*/
48
49/*== STEP 3 ===============================================================
50Fetch records from the database and use them in your frontend component.
51(THIS SNIPPET WILL ONLY WORK IN THE FRONTEND CODE FILE.)
52=========================================================================*/
53
54/* For example, in a React component, you can use this snippet in your
55 function's RETURN statement */
56// const { data: todos } = client.models.Todo.list()
57
58// return <ul>{todos.map(todo => <li key={todo.id}>{todo.content}</li>)}</ul>

The schema generated by Amplify is for a to-do app. A schema is a blueprint for how our app's data will be organized. Within the schema, we will define models that will correspond to a database table—Todo in the above code. Finally, we will define fields, which are attributes that each data instance will have—in the generated code, the field is content. Each field will have a type attached to it—in the above examples, we are stating that the content field is a string.

Step 1: Open amplify/data/resource.ts and update it to add a done field of type boolean and a priority field of enum with a value of ['low', 'medium', 'high']. We've removed the default comments to shorten the code below for the next few examples.

amplify/data/resource.ts
1import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
2
3// ...
4
5const schema = a.schema({
6 Todo: a
7 .model({
8 content: a.string(),
9 done: a.boolean(),
10 priority: a.enum(['low', 'medium', 'high'])
11 })
12 .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])]),
13});
14
15// ...

Once you save your changes to the data model, they will be deployed in seconds to your cloud sandbox.

The Todo data model is defined with authorization rules to allow the person who creates the Todo instance (the owner) to perform all actions on the data they own. We are also allowing all page viewers, including unauthenticated users, to read data.

Note: These authorization rules can be modified using a chain of methods as defined by default. For example, we could remove the .to(['read']) and allow all visitors to perform all actions on data or add permissions for signed-in users or users who belong to user groups such as Admin. You can learn more about all options for authorization in the Customize your auth rules section of the docs.

Step 2: Remove public access by deleting the allow.publicApiKey().to(['read']) authorization rule. Your authorization rule will look like the code below:

amplify/data/resource.ts
1// ...
2
3.authorization(allow => [allow.owner()]),
4
5// ...

Below the schema declaration, you will see the defineData function, which receives our schema and authorization configuration as arguments. The default configuration is for an apiKey to enable public access.

Step 3: Update the defaultAuthorizationMode to userPool so that the default is to use user authentication.

amplify/data/resource.ts
1// ...
2
3export const data = defineData({
4 schema,
5 authorizationModes: {
6 defaultAuthorizationMode: 'userPool'
7 }
8});

Add authentication

Now let's work on our authentication configuration. Similar to the data/resource.ts we just worked on, the auth/resource.ts file has code to define our authentication configuration. In this case, we are setting the authentication method to log in with email.

See the complete amplify/auth/resources.ts
amplify/auth/resource.ts
1import { defineAuth } from '@aws-amplify/backend';
2
3/**
4 * Define and configure your auth resource
5 * When used alongside data, it is automatically configured as an auth provider for data
6 * @see https://docs.amplify.aws/gen2/build-a-backend/auth
7 */
8export const auth = defineAuth({
9 loginWith: {
10 email: true,
11 // add social providers
12 externalProviders: {
13 /**
14 * first, create your secrets using `amplify sandbox secret`
15 * then, import `secret` from `@aws-amplify/backend`
16 * @see https://docs.amplify.aws/gen2/deploy-and-host/sandbox-environments/features/#setting-secrets
17 */
18 // loginWithAmazon: {
19 // clientId: secret('LOGINWITHAMAZON_CLIENT_ID'),
20 // clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'),
21 // }
22 }
23 },
24 /**
25 * enable multifactor authentication
26 * @see https://docs.amplify.aws/gen2/build-a-backend/auth/manage-mfa
27 */
28 // multifactor: {
29 // mode: 'OPTIONAL',
30 // sms: {
31 // smsMessage: (code) => `Your verification code is ${code}`,
32 // },
33 // },
34 userAttributes: {
35 /** request additional attributes for your app's users */
36 // profilePicture: {
37 // mutable: true,
38 // required: false,
39 // },
40 }
41});

Let's customize the subject of the verification email sent to users after they sign up for our app. There is only one step to complete.

Open amplify/auth/resource.ts and update it to add a subject line by defining an object with email authentication properties referencing the code below:

amplify/auth/resource.ts
1// amplify/auth/resource.ts
2import { defineAuth } from '@aws-amplify/backend';
3
4export const auth = defineAuth({
5 loginWith: {
6 email: {
7 verificationEmailSubject: 'Welcome! Verify your email!'
8 },
9 }
10});
The Data and Auth resource files are imported into the amplify/backend.ts file which serves as the entry point to the Amplify backend for all resources used in this app. It is shown below, but there are no modifications needed to complete this quickstart.
amplify/backend.ts
1import { defineBackend } from '@aws-amplify/backend';
2import { auth } from './auth/resource';
3import { data } from './data/resource';
4
5defineBackend({
6 auth,
7 data
8});

Build UI

Let's add UI that connects to the backend data and auth resources.

Add a login form

First, install the Amplify UI component library:

1npm install @aws-amplify/ui-react

Next, configure the Amplify library client-side so it can interact with backend services.

Open src/main.tsx and add the following code below the last import:

src/main.tsx
1import { Amplify } from 'aws-amplify';
2import amplifyconfig from '../amplifyconfiguration.json';
3
4Amplify.configure(amplifyconfig);

Update the src/App.tsx file with the following code to import Amplify UI and wrap your app in the withAuthenticator function:

src/App.tsx
1import { withAuthenticator } from "@aws-amplify/ui-react";
2import "@aws-amplify/ui-react/styles.css";
3
4function App() {
5 return (
6 <>
7 <h1>Hello, Amplify 👋</h1>
8 </>
9 );
10}
11
12export default withAuthenticator(App);

Run your app with npm run dev and navigate to http://localhost:5173. You should now see the authenticator, which is already configured and ready for your first sign-up! Create a new user account, confirm the account through email, and then sign in.

View list of to-do items

Now, let's display data on our app's frontend. Create a src/components directory and then create a TodoList.tsx file within it with the following code:

src/components/TodoList.tsx
1import { useState, useEffect } from "react";
2import { generateClient } from "aws-amplify/data";
3import type { Schema } from "../../amplify/data/resource";
4
5export default function TodoList() {
6 // generate your data client using the Schema from your backend
7 const client = generateClient<Schema>();
8
9 const [todos, setTodos] = useState<Schema["Todo"][]>([]);
10
11 async function listTodos() {
12 // fetch all todos
13 const { data } = await client.models.Todo.list();
14 setTodos(data);
15 }
16
17 useEffect(() => {
18 listTodos();
19 }, []);
20
21 return (
22 <div>
23 <h1>Todos</h1>
24
25 <ul>
26 {todos.map((todo) => (
27 <li key={todo.id}>{todo.content}</li>
28 ))}
29 </ul>
30 </div>
31 );
32}

In src/App.tsx, import components/TodoList and render it:

src/App.tsx
1import { withAuthenticator } from "@aws-amplify/ui-react";
2import "@aws-amplify/ui-react/styles.css";
3import TodoList from "./components/TodoList";
4
5function App() {
6 return (
7 <>
8 <h1>Hello, Amplify 👋</h1>
9 <TodoList />
10 </>
11 );
12}
13
14export default withAuthenticator(App);

Once you save the file and navigate back to http://localhost:5173, you should see a blank page for now because you have only an empty list of to-dos.

Create a new to-do item

Let's update the return statement of the components/TodoList component to have a button for creating a new to-do list item, prompting the user to add the title and description. In a production app, you would want to create a full form, and run the create method on form submission.

src/components/TodoList.tsx
1// ...
2 return (
3 <div>
4 <h1>Todos</h1>
5 <button onClick={async () => {
6 // create a new Todo with the following attributes
7 const { errors, data: newTodo } = await client.models.Todo.create({
8 // prompt the user to enter the title
9 content: window.prompt("title"),
10 done: false,
11 priority: 'medium'
12 })
13 console.log(errors, newTodo);
14 }}>Create</button>
15
16 <ul>
17 {todos.map((todo) => (
18 <li key={todo.id}>{todo.content}</li>
19 ))}
20 </ul>
21 </div>
22 );
23}

Create a couple of to-dos, then refresh the page to see them. You can also subscribe to new to-dos in your useEffect to have them live-reload on the page.

1useEffect(() => {
2 listTodos()
3 const sub = client.models.Todo.observeQuery().subscribe(({ items }) =>
4 setTodos([...items])
5 );
6
7 return () => sub.unsubscribe();
8}, []);

Terminate dev server

Go to localhost in the browser to make sure you can now log in and create and list to-dos. You can end your development session by shutting down the frontend dev server and cloud sandbox. The sandbox prompts you to delete your backend resources. While you can retain your backend, we recommend deleting all resources so you can start clean again next time.

Deploy and host a fullstack branch

Now that your app is working, let's deploy it to a shared fullstack branch so you can share the project with your team. Amplify offers a fully managed hosting service with CI/CD built in, making it easy to set up production and staging environments with Git branches. In Gen 2, every Git branch in your repository maps 1:1 to a fullstack branch in Amplify.

Create remote Git repository

If you already have your project remotely hosted in a Git repository, you can skip this step. Otherwise, navigate to your preferred Git provider, and add your project to a new repository. Amplify supports GitHub, AWS CodeCommit, GitLab, and Bitbucket.

Connect branch in the Amplify console

  1. To get started with Gen 2, log in to the Amplify console and navigate to your preferred AWS Region. (The Amplify console is available in 19 AWS Regions).
  2. From the Public Preview banner, choose Try Amplify Gen 2.

Amplify console showing the "Public Preview" banner with "Try Amplify Gen 2" button

  1. In the Git provider screen, choose Option 2: Start with an existing app. Connect the repository you just deployed and pick a branch.

  2. Review all of your settings to ensure everything is set up correctly. Choose Save and deploy to deploy your web app.

Manage fullstack branch

The new Amplify console gives you a central place to manage your branches, hosting settings, CI/CD builds, and backend resources. Even though you can access backend resources directly from AWS service consoles, the Amplify console offers built-in user and data administration.