All Posts

  • Published on
    I recently faced a challenge: I needed a way to extract email addresses from PDFs in my Next.js v14 app while ensuring only authenticated users with an active subscription could access the functionality. After trying several approaches, I settled on a solution that combines Supabase for auth, a secure API route for PDF processing, and **pdf2json** for parsing the PDF content. In this post, I'll walk you through how to build a secure PDF email extractor—from setting up the authentication to processing PDFs on the server side. ## 📚 Prerequisites & Dependencies Before diving in, make sure you have: - A Next.js v14 project with App Router enabled - Supabase configured for authentication with environment variables set up: ```bash NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key ``` - The following dependencies installed: ```bash npm install pdf2json uuid @supabase/supabase-js ``` ## Setting Up Authentication & Subscription Checks The first step in our API route is to verify that the user is authenticated and has an active subscription. Here's how we implement these checks: ```typescript export async function POST(request: Request) { // Initialize Supabase client const supabase = createClient(); const { data: { session }, } = await supabase.auth.getSession(); // Check for valid session if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // Verify subscription status const subscription = await getUserSubscription(); if (!subscription?.isActive) { return NextResponse.json({ error: "Subscription required" }, { status: 403 }); } ``` This code ensures that only authenticated users with active subscriptions can access our PDF processing functionality. If either check fails, we return an appropriate error response. ## Handling File Upload & Validation Once we've verified the user's access, we need to handle and validate the uploaded PDF file. We'll check both the file type and size: ```typescript const file = formData.get("pdf"); if (!file || typeof file === "string") { return NextResponse.json({ error: "No file provided" }, { status: 400 }); } if (file.type !== "application/pdf") { return NextResponse.json({ error: "Only PDF files are allowed" }, { status: 400 }); } if (file instanceof File && file.size > MAX_FILE_SIZE) { return NextResponse.json({ error: "File size exceeds limit" }, { status: 400 }); } ``` This validation ensures we're only processing appropriate PDF files and helps prevent potential security issues or resource exhaustion. ## Processing the PDF After validation, we need to temporarily save the file and process it. We use `uuid` to generate unique filenames and `pdf2json` to extract the text content: ```typescript const fileName = uuidv4(); const tempFilePath = `/tmp/${fileName}.pdf`; const fileBuffer = Buffer.from(await file.arrayBuffer()); await fs.writeFile(tempFilePath, fileBuffer); const pdfParser = new (PDFParser as any)(null, 1); const pdfData = await new Promise((resolve, reject) => { pdfParser.on("pdfParser_dataError", reject); pdfParser.on("pdfParser_dataReady", () => { resolve(pdfParser.getRawTextContent()); }); pdfParser.loadPDF(tempFilePath); }); ``` Notice how we use event listeners to handle both successful parsing and potential errors. This ensures we can properly respond to any issues that might arise during PDF processing. ## Extracting Email Addresses Once we have the raw text content, we can extract email addresses using a regular expression. We also make sure to remove any duplicates: ```typescript const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}/g; const matches = (pdfData as string).match(emailRegex) || []; const uniqueEmails = Array.from(new Set(matches)); await fs.unlink(tempFilePath); // Clean up temp file return NextResponse.json({ emails: uniqueEmails }); ``` The regex pattern matches standard email formats, and using `Set` ensures we don't return duplicate addresses. ## Error Handling & Cleanup It's crucial to clean up temporary files, even if an error occurs during processing. Here's how we handle errors: ```typescript try { // PDF processing code here } catch (error) { await fs.unlink(tempFilePath); // Ensure cleanup on error return NextResponse.json({ error: "Error parsing PDF" }, { status: 500 }); } ``` This try-catch block ensures we don't leave any temporary files on the server, regardless of whether the processing succeeds or fails. ## Implementing the Frontend While the backend handles the heavy lifting, we need a user-friendly way to upload PDFs. Here's a simple upload component using shadcn-ui: ```typescript import { Upload } from "lucide-react" import { Button } from "@/components/ui/button" export function UploadButton() { const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => { const file = event.target.files?.[0]; if (!file) return; const formData = new FormData(); formData.append("pdf", file); try { const response = await fetch("/api/upload-pdf", { method: "POST", body: formData, }); const data = await response.json(); if (data.emails) { toast.success(`Found ${data.emails.length} email addresses!`); } } catch (error) { toast.error("Error processing PDF"); } }; return ( <Button variant="outline" size="sm"> <Upload className="mr-2 h-4 w-4" /> Upload PDF <input type="file" accept=".pdf" className="hidden" onChange={handleUpload} /> </Button> ); } ``` ## Wrapping Up This solution provides a secure and efficient way to extract emails from PDFs in a Next.js application. By combining Supabase authentication, server-side PDF processing, and proper error handling, we've created a robust system that: - Only allows authenticated users with active subscriptions to access the functionality - Safely handles file uploads and processing - Properly cleans up temporary files - Provides a smooth user experience The complete solution is production-ready and can be extended to handle additional use cases, such as processing multiple PDFs simultaneously or extracting different types of data. I hope you found this guide helpful! If you have any questions or suggestions, feel free to reach out. Happy coding! 🚀
  • Published on
    In this post, we’ll walk through how to implement a password recovery system using Next.js 14 and Supabase. We'll cover setting up an API route to handle the password reset link, and client-side pages for users to request a password reset and set a new password. ### Step 1: Configure Supabase Client Ensure you have the Supabase client set up using the pkce flow. We’ll use it in our API routes and client-side code. ```typescript // supabase/pkce.ts import { createBrowserClient } from "@supabase/ssr"; export const supabase = createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { auth: { detectSessionInUrl: true, flowType: "pkce", }, } ); ``` ### Step 2: Create the Confirmation API Route This API route will handle the confirmation link sent to the user’s email. When a user clicks on the link they will receive via email, they will get redirected to this api route that verifies their token and redirects them to the password reset page. ```typescript // pages/api/auth/confirm.ts import { NextResponse } from "next/server"; import { createClient } from "@/lib/supabase/server"; import { absoluteUrl } from "@/lib/utils/absoluteUrl"; import { Routes } from "@/types"; export async function GET(req: Request) { const supabase = createClient(); const url = new URL(req.url); const token_hash = url.searchParams.get("token_hash"); const type = url.searchParams.get("type"); if (!token_hash || type !== "email") { return NextResponse.json({ error: "Invalid request" }, { status: 400 }); } // Here is were we verify the token_hash so that our users // will later be able to update their password. const { data: { session }, error, } = await supabase.auth.verifyOtp({ token_hash, type }); if (error) { return NextResponse.json({ error: error.message }, { status: 400 }); } // Redirect to the specified URL which in our case is // the app/password-reset/page.tsx, where users will set the new password // after they have been authenticated using the verifyOtp method return NextResponse.redirect(absoluteUrl(Routes.passwordReset)); } ``` ### Step 3: Set up api route to send an email to the user Before we allow users to update their password, we need to generate a password reset link using Supabase that Supabase will email to our users. For production sites, it's best to set up a custom SMTP server. ```typescript // /api/reset-password/route.ts import { NextResponse } from "next/server"; import { createClient } from "@/lib/supabase/server"; import { BASE_URL } from "@/constants"; const options = { redirectTo: BASE_URL, // https://your-site.com }; export async function POST(req: Request) { const supabase = createClient(); const { email } = await req.json(); if (!email) { return NextResponse.json({ error: "Email is required" }, { status: 400 }); } try { // Generate a password reset link using Supabase and email it to the user const { data, error } = await supabase.auth.resetPasswordForEmail( email, options ); if (error) { return NextResponse.json({ error: error.message }, { status: 400 }); } return NextResponse.json(data); } catch (error) { return NextResponse.json({ error }); } } ``` ### Step 4: Set Up the Email Template in Supabase Now we need to set up the email template to point to an api route in our Next.js application. In the Supabase Dashboard, navigate to the "Authentication" settings and configure the `reset password` email template to point to your confirmation endpoint. Notice the `/api/auth/confirm?` part, that points to the api route we set up in Step 2: ```typescript <div style="font-family: Arial, sans-serif; line-height: 1.5; color: #333; min-height:100vh"> <h2 style="color: #4A90E2;">Password Recovery</h2> <p>Follow this link to reset the password for your user:</p> <p><a href="{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=email&redirectUrl={{ .RedirectTo }}">Reset Password</a></p> <p>If you did not request this, please ignore this email.</p> </div> ``` ### Step 5: Password Reset Client-Side Implementation Create a page where users can actually set their new password. If all goes well, on the api route code, we will be redirected there. Here we use zod and useForm, but this is not essential for this to work. ```typescript // app/password-reset/page.tsx "use client"; import * as z from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; import { supabase } from "@/lib/supabase/pkce"; import { Routes } from "@/types"; export const PasswordReset = () => { const [pending, startTransition] = useTransition(); const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), defaultValues: { password: "", }, }); const handleSetPassword = (data: z.infer<typeof FormSchema>) => { const parsedData = FormSchema.shape.password.safeParse(data.password); if (!parsedData.success) { toast.error("Please use at least 8 characters for your password."); return; } startTransition(async () => { try { // here we update the password using the updateUser method const { error } = await supabase.auth.updateUser({ password: parsedData.data, }); if (error) { console.error("Password error:", error); toast.error("Something went wrong. Please try again."); return; } toast.success("Password updated successfully 🎉"); } catch (error) { console.error("Form error:", error); toast.error("Something went wrong. Please try again.", { duration: 3000, }); return; } // redirect if we are successful window.location.href = Routes.home; }); }; return ( <Form {...form}> <form onSubmit={form.handleSubmit(handleSetPassword)} className="flex flex-col gap-4 mx-auto"> <FormField control={form.control} name="password" render={({ field }) => { return ( <FormItem> <FormLabel className="font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-xl tracking-wide leading-loose"> New Password </FormLabel> <FormControl> <Input {...field} type="password" placeholder="8+ characters" autoComplete={"8+ characters"} disabled={pending} aria-disabled={pending} className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" /> </FormControl> <FormMessage /> </FormItem> ); }} <Button type="submit">Reset Password</Button> </form> </Form> ); }; export default PasswordReset; ``` ### Step 6: Utility Functions and Types Ensure you have necessary utility functions and types in place. ```typescript // lib/utils/absoluteUrl.ts export const absoluteUrl = (path: string) => `${process.env.NEXT_PUBLIC_SITE_URL}${path}`; // types/index.ts export const Routes = { home: "/", passwordReset: "/reset-password", // ...rest of the routes }; export enum APIRoutes { passwordRecoveryEmail = "/api/reset-password", // ... rest of the api routes } ``` ### Step 7: Client-Side Code for Requesting Password Reset This component will allow users to input their email address and request a password reset. Supabase will then send an email with the reset link. ```typescript // we will import and render this component in --> app/password-recovery/page.tsx "use client"; import * as z from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import Link from "next/link"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { APIRoutes, Routes } from "@/types"; import { useTransition } from "react"; import { LoaderCircleIcon } from "lucide-react"; import { useRouter, useSearchParams } from "next/navigation"; import { SuccessMessage } from "./success-message"; import { useRedirect } from "@/lib/hooks/useRedirect"; import { useInvalidTokenError } from "@/lib/hooks/useInvalidTokenError"; import { absoluteUrl } from "@/lib/utils/absoluteUrl"; const FormSchema = z .object({ email: z.string().email(), }) .strict(); type Props = { buttonText: string; headingText: string; paragraphText: string; emailAutoComplete: string; }; export const PasswordRecoveryForm = ({ buttonText, headingText, paragraphText, emailAutoComplete, }: Props) => { const router = useRouter(); const [pending, startTransition] = useTransition(); const { showSuccessMessage, setShowSuccessMessage } = useRedirect( router, 5000 ); const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), defaultValues: { email: "", }, }); const handleResetPassword = async (data: z.infer<typeof FormSchema>) => { const parsedData = FormSchema.shape.email.safeParse(data.email); if (!parsedData.success) { toast.error("Invalid email. Please check your input."); return; } startTransition(async () => { try { const response = await fetch(APIRoutes.passwordRecoveryEmail, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: parsedData.data, }), }); if (!response.ok) { toast.error("Something went wrong. Please try again.", { duration: 5000, }); } else { setShowSuccessMessage(true); } } catch (error) { console.error("Reset password error:", error); toast.error("Something went wrong. Please try again.", { duration: 5000, }); } }); }; if (showSuccessMessage) { return <SuccessMessage />; } return ( <div className="bg-white p-12 flex flex-col justify-center mx-auto"> <div className="mt-12"> <h2 className="text-3xl font-bold mb-4">{headingText}</h2> <p className="text-gray-600 mb-8">{paragraphText}</p> <Form {...form}> <form onSubmit={form.handleSubmit(handleResetPassword)}> <div className="space-y-4 mb-4"> <FormField control={form.control} name="email" render={({ field }) => { return ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input placeholder="johndoe@example.com" type="email" autoComplete={emailAutoComplete} {...field} disabled={pending} aria-disabled={pending} /> </FormControl> <FormMessage /> </FormItem> ); }} /> </div> <div className="flex items-center space-x-4 mb-4 mx-auto"> <Button className="bg-[#bd1e59] text-white relative flex-1 text-base" type="submit" disabled={pending} aria-disabled={pending} > {pending && ( <LoaderCircleIcon className="h-5 w-5 text-white animate-spin absolute left-36" /> )} {buttonText} </Button> <Link href={absoluteUrl(Routes.login)} prefetch className="flex-1 text-center text-base text-blue-500 hover:underline" > Return to login </Link> </div> </form> </Form> </div> </div> ); }; ``` ### Conclusion With these steps, you've set up a complete password recovery system using Next.js 14 and Supabase. Users can request a password reset, receive an email with a link, and set a new password through the provided form. This approach ensures a secure and user-friendly password recovery flow in Next.js. Feel free to customize the UI and enhance the functionality as needed. Happy coding!
  • Published on

    Connect to the AppSync API with React

    GraphQLServerlessAppSyncReact
    This is the third post in a multi-part series that shows you how to install and configure a serverless web app on AWS using AWS Amplify and AppSync for the backend and Create React App for the frontend. ## Configuring your React app: To connect to the new API, we first need to configure our React project with our Amplify project credentials. In your `src` folder there is a file called `aws-exports.js`. This file holds all of the information our local project needs to know about our cloud resources.As I mentioned in my previous [post](https://www.yannisspyrou.com/posts/get-started-with-aws-amplify-and-react), this file should be added to a `.gitignore` file, so if you haven't done so already, go ahead and run the following command from your terminal: ```shell $ echo "aws-exports.js" >> .gitignore ``` To configure the React app, open `src/app.js` &amp; add the following lines: ```js import { withAuthenticator } from 'aws-amplify-react'; import Amplify from 'aws-amplify'; import config from 'aws-exports'; Amplify.configure(config); ``` The last line tells Amplify to use the `aws-exports.js` file to connect to the correct backend API. You'll notice however that we have some imports that have to do with authentication. ```jsx import { withAuthenticator } from 'aws-amplify-react'; ``` ## withAuthenticator HOC `withAuthenticator` is a helper function provided by Amplify so we can use the out-of-the-box solution that AWS provides us with for auth services. The `App.js` file should now look something like this: ```jsx import React from 'react'; import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> <p className="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } export default App; ``` What we'll need to do now is change that last line and paste the line below: ```jsx export default withAuthenticator(App, true); ``` To test if everything works as intended, we can run `yarn start` or `npm start` and see if we are presented with the default AWS login UI. <img class="mt-4 mb-4" src="/assets/postsImages/amplify-login.png" alt="Amplify login component with a username and a password input and a sign in button" /> In order for us to use our app we now have to register a new user to our API, by clicking the 'Create Account' link at the bottom. Once we're done registering, AWS will send a confirmation link to the email address we registered. After that, we are going to be able to login and see the React app in all its glory! ## Setting up Apollo Client for real-time data One of AppSync's greatest features is its ability for real-time data that is powered by GraphQL subscriptions. Instead of implementing this with WebSockets like Apollo Client does, AppSync’s subscriptions use MQTT as the transport layer. If you want to take advantage of this excellent feature all you need to do is configure your Apollo client. First step is to create an `ApolloClient.js` file within your `src/graphql` folder that will have the contents below: ```jsx // src/graphql/ApolloClient.js import { Auth } from 'aws-amplify'; import { InMemoryCache } from 'apollo-cache-inmemory'; import AWSAppSyncClient from 'aws-appsync'; import config from '../aws-exports'; const client = new AWSAppSyncClient({ url: config.aws_appsync_graphqlEndpoint, region: config.aws_appsync_region, auth: { type: config.aws_appsync_authenticationType, jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(), }, complexObjectsCredentials: () => Auth.currentCredentials(), cache: new InMemoryCache(), }); export default client; ``` For more details on the configuration of the AWSAppSyncClient head over to the Amplify's [configuration options section](https://aws-amplify.github.io/docs/js/api#configuration-options). After you have saved this file, you need to import the `client` to the `App.js` file, so we can use this in our Apollo Provider. ```jsx // src/App.js import { withAuthenticator } from 'aws-amplify-react'; // Add the following lines to your App.js imports import { Rehydrated } from 'aws-appsync-react'; import { ApolloProvider } from 'react-apollo'; import Amplify from 'aws-amplify'; import config from 'aws-exports'; Amplify.configure(config); function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> <p className="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } const WithProviders = () => ( <ApolloProvider client={client}> <Rehydrated> <App /> <Rehydrated /> <ApolloProvider /> ); export default withAuthenticator(WithProviders, true); ``` What happened there, is that we wrapped the `App` component with `Rehydrated` and the `ApolloProvider`, so that we can get advantage of the AWSAppSync Client. It takes a `client` prop and this is where we pass on the client we configured earlier in our `ApolloClient.js` file. ## Congratulations! You have now successfully connected to the AppSync API from your React app and leveraged AppSync's implementation of Apollo Client that enables real-time capabilities in your app!Stay tuned for the next part of this series that will take you through querying data from our React App using [React Hooks](https://reactjs.org/docs/hooks-intro.html). <div class="mt-4 space-y-4"> <p>Do you think something is missing from this article?</p> <div> <a class="text-teal-500" href="https://twitter.com/SpyrouYannis"> Let me know on Twitter. </a> </div> </div>
  • Published on

    AppSync Backend Setup & React

    AppSyncServerlessReactGraphQL
    In this post we will see how to install and configure a serverless web app on AWS using AWS Amplify and AppSync for the backend and Create React App for the frontend. ## Add AppSync API and DynamoDB Database: Add a GraphQL API to your app and automatically provision a database with the following command (accepting all defaults is OK): ```shell $ amplify add api ``` This command will run you through the creation of the AWS AppSync GraphQL API. ```shell ? Please select from one of the below services: GraphQL ? Provide API name: mygreatapp ? Choose an authorization type for the API: AMAZON_COGNITO_USER_POOLS ? Do you have an annotated GraphQL schema?: N ? Do you want a guided schema creation? Y ? What best describes your project? One-to-many relationship ? Do you want to edit the schema now? Y ``` This will open a GraphQL Schema in your editor. Since this tutorial is meant to help you with a simple setup, we are not going to make any changes to our GraphQL schema for the time being. After you have saved the file, head back to your terminal and press Enter to finish the setup. This is what you should see after finishing the process: ```shell GraphQL schema compiled successfully. Edit your schema at /dev/amplify/backend/api/dev/schema.graphql or place .graphql files in a directory at /dev/amplify/backend/api/dev/schema Successfully added resource dev locally ``` Now a new folder, named `amplify` should have appeared at the root of your project. This means that our api is now setup in our computer, but what about the remote server? All we need to do, to set this up is run the following command from our terminal: ```shell $ amplify push ``` This command will go ahead and provision the remote server for us. The whole process should take roughly 15 minutes, so you have plenty of time for a coffee. ☕☕☕ To verify that the CLI is set up correctly, run the following command: ```shell $ amplify status ``` If all went well, the Amplify CLI should output the following status table with no resources listed: ```shell | Category | Resource Name | Operation | Provider Plugin | | ---------| --------------------- | ---------- | --------------- | | Auth | mygreatappac234765 | No Change | awscloudformation | | Api | mygreatapp | No Change | awscloudformation | ``` Under the operation column, you should be able to see that there is no changes. That means that we have successfully provisioned our remote server and our local and remote environments are in sync! The Auth column means we can use an Amazon Cognito User Pool to act as the backend that users will be able to sign up and sign in. If you want to read more on this, have a look at the ‘Authentication’ steps from the [AWS Amplify Authentication guide](https://aws-amplify.github.io/docs/js/authentication). ### Testing our API Once we've verified our resources have been created, we’re ready to create the app & begin interacting with the API. Apart from testing our API through the [AppSync dashboard](https://console.aws.amazon.com/appsync/home), we can test it locally as well! All we need to do is run the following command and a local AppSync mock endpoint will start up: ```shell $ amplify mock ``` The terminal will output a link to an instance of the GraphiQL IDE, where you can see the GraphQL docs on any field or type and test your queries and mutations. For more information on Amplify's local framework testing please visit [this link](https://aws.amazon.com/blogs/mobile/amplify-framework-local-mocking/). ## 🎉 Congratulations! You have now successfully provisioned the remote servers and hopefully were able to test all your resources locally using the GraphiQL IDE. Stay tuned for the next part of this series that will take you through integrating AWS into your React app. <div class="mt-4 space-y-4"> <p>Do you think something is missing from this article?</p> <div> <a class="text-teal-500" href="https://twitter.com/SpyrouYannis"> Let me know on Twitter. </a> </div> </div>
  • Published on

    Get started with AWS Amplify and React

    AmplifyServerlessReactGraphQLAppSync
    This post is a quick guide on how to install and configure a serverless web app on AWS using AWS Amplify and AppSync for the backend and Create React App for the frontend. AWS Amplify is a JavaScript library for frontend and mobile developers building cloud-enabled applications. AWS AppSync is a fully managed GraphQL service with real-time data synchronisation and offline programming features. <br /> In this first post, you will learn how to: <br /> - Install &amp; configure the Amplify CLI from your command line - Initialise an Amplify project with a React app ## Prerequisites - Mac or Win10 local development environment node version 10.x or greater - `npm` version 5.x or greater (comes with npx) or `yarn` - AWS Account with appropriate permissions to create the related resources ## Amplify CLI &amp; React app installation To install AWS Amplify CLI run the below command: ```shell $ npm install -g @aws-amplify/cli ``` Next step is scaffolding our React app. ```bash $ npx create-react-app mygreatapp $ cd mygreatapp $ npm install -g @aws-amplify ``` We will also need to install React-specific Amplify and AppSync components, by running the following command: ```shell $ npm install aws-amplify aws-amplify-react aws-appsync aws-appsync-react or $ yarn add aws-amplify aws-amplify-react aws-appsync aws-appsync-react ``` ## Amplify configure Next command is a one-time setup step. Going forward on new projects, we won't have to configure this again. ```shell $ amplify configure ``` The amplify configure step helps you with the following: - Signing up and signing in into AWS - Setting up an IAM user with the appropriate policies for the CLI to deploy AWS resources on our behalf. - Creating an AWS Profile on your local system with reference to the accessKey and secretKey tied to the IAM user created in the above step. This AWS Profile could be given a custom name and can be used for initialising any number of projects moving forward. Essentially the AWS profile defines which AWS account and region the AWS resources would be deployed to. Once we run `amplify configure`, the CLI will take us through the initial configuration. ```shell - ? region: `<your-region-of-choice>` - ? user name: `<your.email>@example.com` - ? accessKeyId: `<'Access key ID' from credentials.csv>` - ? secretAccessKey: `<'Secret access key' from credentials.csv>` - ? Profile Name: `default` ``` Before initialising our Amplify project,we need to make sure we are in the right directory in our React app. In my case all I need to do is: ```shell $ cd mygreatapp ``` ## Amplify init Next we'll need to run the *amplify init* command. This command will run you through the init process. Go ahead and accept the defaults and use ‘dev’ or `test` as environment name. ```shell $ amplify init ``` This setup helps us with the following: - Selecting our AWS Profile which would be used to provision cloud resources for your app - Selecting the frontend framework for our app and corresponding frontend framework related configurations. ```shell - ? Do you want to use an existing environment? Yes - ? Choose the environment you would like to use: dev - ? Choose your default editor: Your preferred editor - ? Do you want to use an AWS profile? Yes - ? Please choose the profile you want: default ``` This will automatically generate a `aws-exports.js` file under your `src` directory. This file holds all security credentials for your API so you want to keep this safe. On your terminal, run the following command: ```shell $ echo "aws-exports.js" >> .gitignore ``` The above command will create a `.gitignore` file and append the `aws-exports.js` line to it so it doesn't accidentally get commited to your repo. ## Amplify status To verify that the CLI is set up for your React app, run the following command: ```shell $ amplify status ``` If all went well, the Amplify CLI should output the following status table with no resources listed: ```shell | Category | Resource Name | | Operation | Provider Plugin | | ---------| ------------- | | ---------- | --------------- | ``` 🎉 That was it! You have successfully initialised a cloud project using Amplify. Stay tuned for the next part of this series that will take you through the provisioning of the remote servers. <div class="mt-4 space-y-4"> <p>Do you think something is missing from this article?</p> <div> <a class="text-teal-500" href="https://twitter.com/SpyrouYannis"> Let me know on Twitter. </a> </div> </div>

Yannis Spyrou

© All rights reserved | 2025
GithubTwitterLinkedin