How I Built an E-commerce Platform like Shopify in 3 Weekends with AWS Amplify
When Ambition meets AWS Amplify
Table of contents
- Introduction
- Before moving forward, Let's see the features of the app
- Let's get into the details
- Routes configured in the app
- App's Layout
- Models Generated via Amplify Studio
- How authentication works in the app
- Add a Storage feature in your app using StorageManager from Amplify UI library
- Track user actions using AWS Pinpoint API
- Deploy your app automatically by just adding your git repository to the amplify console
- How to setup In-App messaging using AWS Pinpoint
- Things to keep in mind while working with Amplify
- Conclusion
- Thanks for reading until the end
Before we dive into the nitty-gritty details of how I built this Shopify alternative app using AWS Amplify, I want to apologize for the slightly clickbaity title. While the core facts are true - I did build this app in roughly 70 hours over two weeks - I recognize that headlines like "How I Built X in Y Time" can come across as exaggerated or misleading.
My intention with the title was simply to convey the ambitious yet achievable nature of this project. With the right technology stack and approach, even complex web applications can be built surprisingly quickly these days. But I don't want you to walk away thinking I somehow hacked time or defied the laws of software development! The title is meant to pique your interest and get you curious about the actual story, not mislead you in any way.
So with that out of the way, let's dive into the real nitty gritty. I'll walk you through the problem I set out to solve, the technology choices I made, the challenges I faced, and - of course - how I built this Shopify alternative app in just three crazy weekends. I hope my story inspires you to tackle your ambitious weekend projects, armed with the right tools and strategies to succeed.
Introduction
If you've ever dreamed of building a full-stack app but have been intimidated by the technical challenges of setting up the whole backend, this story is for you.
Using AWS Amplify, I quickly generated the frontend components I needed - from forms and tables to modals and menus. Amplify Studio's drag-and-drop interface allowed me to build the app's data models and APIs in just a few hours.
Behind the scenes, Amplify is handling all the backend work for me - hosting my app, managing the database, and integrating features like analytics and cloud storage. This freed me up to focus on the user experience and functionality that matters most for the users.
The result? An all-in-one e-commerce platform that helps users:
• Create and manage their product catalog, customers, and orders.
• Store product images and documents in the cloud
• Send targeted promotional emails to the right customers
• Track sales, orders, and other key metrics
In the following article, I'll walk you through how I built this app in just 3 crazy weekends. I hope that by sharing my story and lessons learned, I'll inspire you to tackle your ambitious weekend projects.
Before moving forward, Let's see the features of the app
🔐 Secure Login/Signup with AWS Cognito - Amplify makes it a breeze to integrate user authentication and authorization using AWS Cognito. Users can quickly create accounts and log in to access the app's features.
📝 GraphQL API - Amplify provides a GraphQL API out of the box to interact with the front end. This allowed me to quickly build the data models and CRUD operations for products, orders, and customers.
⚛️ Amplify UI Components - I leveraged Amplify's React UI library to generate forms, tables, modal windows, and other UI components - saving me hours of frontend development work.
🎨 Amplify Studio Data Models - Amplify Studio's visual interface helped me design and generate the data models for my backend, which I then connected to my React frontend.
📧 AWS SES + Pinpoint Email Campaigns - I integrated AWS SES and Pinpoint to enable targeted promotional email campaigns to the right customers at the right time.
📊 Built-in Analytics with Pinpoint - Amplify's integration with Pinpoint gave me real-time analytics and metrics like total auto-tracking a page time spent, auto-tracking user sessions, and page views - right out of the box.
🛠 Check out the code and demo:
💻 GitHub Repo: https://github.com/vishesh-baghel/amplify-hackathon
🖥 Live Demo: https://main.d324liwg52d96r.amplifyapp.com/
Login credentials- email: test@mail.com, password: Test@123
Let's get into the details
Routes configured in the app
function MyRoutes() {
const { route } = useAuthenticator((context) => [context.route]);
Storage.configure({ track: true });
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route
index
element={route === "authenticated" ? <Dashboard /> : <Home />}
/>
<Route
path="/cart"
element={
<RequireAuth>
<Cart />
</RequireAuth>
}
/>
<Route path="/collections" element={<Collections />} />
<Route
path="/products"
element={
<RequireAuth>
<ProductSummary />
</RequireAuth>
}
/>
<Route
path="/customers"
element={
<RequireAuth>
<CustomerSummary />
</RequireAuth>
}
/>
<Route
path="/orders"
element={
<RequireAuth>
<OrderSummary />
</RequireAuth>
}
/>
<Route
path="/storage"
element={
<RequireAuth>
<StorageSummary />
</RequireAuth>
}
/>
<Route
path="/marketing"
element={
<RequireAuth>
<Marketing />
</RequireAuth>
}
/>
<Route
path="/analytics"
element={
<RequireAuth>
<Analytics />
</RequireAuth>
}
/>
<Route
path="/profile"
element={
<RequireAuth>
<Profile />
</RequireAuth>
}
/>
<Route path="/login" element={<Login />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function App() {
return (
<Authenticator.Provider>
<MyRoutes />
</Authenticator.Provider>
);
}
export default withInAppMessaging(App);
we will get into what withInAppMessaging(App)
is, in the upcoming sections.
App's Layout
export function Layout() {
useEffect(() => {
Analytics.autoTrack("event", {
enable: true,
events: ["click"],
selectorPrefix: "data-amplify-analytics-name",
provider: "AWSPinpoint",
});
}, []);
const { route } = useAuthenticator((context) => [
context.route,
context.signOut,
]);
const [userProfilePhoto, setUserProfilePhoto] = React.useState("");
const navigate = useNavigate();
React.useEffect(() => {
getUserInfo();
}, []);
async function getUserInfo() {
const user = await Auth.currentAuthenticatedUser();
downloadFile(user);
}
const downloadFile = async (user) => {
await Storage.get(`${user.attributes.picture}`, {
level: "public",
})
.then((result) => {
setUserProfilePhoto(result);
})
.catch((err) => console.log(err));
};
return (
<View className={style.container}>
{route !== "authenticated" ? (
<NavBarHeader2
width={"100%"}
className={style.navbar}
loginHandler={() => navigate("/login")}
data-amplify-analytics-name="login"
/>
) : (
<>
<NavBarHeader
width={"100%"}
className={style.navbar}
cartHandler={() => navigate("/cart")}
profileImage={userProfilePhoto}
data-amplify-analytics-name="cart"
/>
</>
)}
<Outlet />
</View>
);
}
Let's see what this code this doing:
useEffect(() => {
Analytics.autoTrack("event", {
enable: true,
events: ["click"],
selectorPrefix: "data-amplify-analytics-name",
provider: "AWSPinpoint",
});
}, []);
Inside the component, there is a useEffect hook that enables auto-event tracking using Amplify's Analytics API. It sets up the tracking to capture click events on elements with the data-amplify-analytics-name attribute and uses AWS Pinpoint as the analytics provider. See the Amplify docs here for more details on how the analytics works in Amplify.
Models Generated via Amplify Studio
type AuditLogs @model @auth(rules: [{allow: public}]) {
id: ID!
model: String
opType: String
}
type Cart @model @auth(rules: [{allow: public}]) {
id: ID!
createdAt: AWSTimestamp
updatedAt: AWSTimestamp
Products: [Product] @hasMany(indexName: "byCart", fields: ["id"])
}
type Inventory @model @auth(rules: [{allow: public}]) {
id: ID!
quantity: Int
location: Address
lastUpdated: AWSTimestamp
productID: ID! @index(name: "byProduct")
}
type Product @model @auth(rules: [{allow: public}]) {
id: ID!
name: String
description: String
price: String
category: String
productTags: [String]
Inventories: [Inventory] @hasMany(indexName: "byProduct", fields: ["id"])
cartID: ID @index(name: "byCart")
productImages: [String]
}
type OrderItem {
id: ID
quantity: Int
price: Float
productID: ID
}
type Order @model @auth(rules: [{allow: public}]) {
id: ID!
shippingAddress: Address
billingAddress: Address
totalAmount: Float
status: String
items: [OrderItem]
customerID: ID! @index(name: "byCustomer")
}
type Address {
id: ID
recipientName: String
street: String
city: String
state: String
postalCode: String
country: String
}
type Customer @model @auth(rules: [{allow: public}]) {
id: ID!
name: String!
dateOfBirth: AWSDate
email: AWSEmail
billingAddress: Address
shippingAddress: [Address]
profileImage: String
gender: String
Orders: [Order] @hasMany(indexName: "byCustomer", fields: ["id"])
Cart: Cart @hasOne
}
One interesting thing to notice here is AuditLogs
. I am using DataStore.observe()
method to listen to all the changes happening in any specific model. For example, I am listening to all the changes happening in the Customer
model in the below code:
useEffect(() => {
const customerSub = DataStore.observe(Customer).subscribe((msg) => {
DataStore.save(
new AuditLogs({
model: "Customer",
opType: msg.opType.toString(),
})
)
.then(() => console.log("saved customer"))
.catch((err) => console.log(err));
});
return () => {
customerSub.unsubscribe();
};
});
To know more about DataStore, see these docs from Amplify.
You can easily create or edit your models as per your requirements using the amplify studio visual editor for models. For example, the customer model for this app looks something like this in the console.
you can easily add more fields to the model. Also, you can create relationships and associations with other models and types to make your database more sophisticated.
How authentication works in the app
export function Login() {
const { route } = useAuthenticator((context) => [context.route]);
const location = useLocation();
const navigate = useNavigate();
let from = location.state?.from?.pathname || "/";
useEffect(() => {
if (route === "authenticated") {
navigate(from, { replace: true });
}
}, [route, navigate, from]);
return (
<View className="auth-wrapper">
<Authenticator></Authenticator>
</View>
);
}
All you have to do is add this login component and set up a route for that in your app and Amplify will take care of everything related to authentication. From login to signup, everything is out-of-the-box just like magic. Your login page will look something like this-
you can also configure different fields(like phone number, gender, address, etc.) you want to include at the time of account creation. See these docs.
My sign-up forms look something like this-
As you can see. I have added a custom attribute name
in the sign-up form.
Add a Storage feature in your app using StorageManager from Amplify UI library
<StorageManager acceptedFileTypes={["image/png", "video/*",]}
accessLevel="public"
maxFileCount={5}
maxFileSize={10000000}
isResumable
processFile={processFile}
/>
const processFile = async ({ file }) => {
const fileExtension = file.name.split(".").pop();
return file
.arrayBuffer()
.then((filebuffer) => window.crypto.subtle.digest("SHA-1", filebuffer))
.then((hashBuffer) => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((a) => a.toString(16).padStart(2, "0"))
.join("");
return { file, key: `${hashHex}.${fileExtension}` };
});
};
Here, the processFile
the function will generate a unique key for all your files uploaded to the aws S3 bucket.
This component will render the file uploader which looks like this-
I have wrapped the StorageManager
component using Card
component for adding details like the file size and accepted extensions. You can also add custom CSS and other props to StorageManager
. Visit these docs for more details.
Track user actions using AWS Pinpoint API
const [startTime, setStartTime] = useState(null);
const [endTime, setEndTime] = useState(null);
useEffect(() => {
setStartTime(new Date());
return () => setEndTime(new Date());
}, []);
useEffect(() => {
if (startTime && endTime) {
const seconds = (endTime.getTime() - startTime.getTime()) / 1000;
Analytics.record({
name: "timeOnCollectionPage",
attributes: { timeOnPage: seconds },
});
}
Analytics.autoTrack("session", {
enable: true,
attributes: {
page: "Dashboard",
},
});
Analytics.autoTrack("pageView", {
enable: true,
eventName: "DashboardPageView",
type: "singlePageApp",
provider: "AWSPinpoint",
getUrl: () => {
return window.location.origin + window.location.pathname;
},
});
Analytics.autoTrack("event", {
enable: true,
events: ["click"],
selectorPrefix: "data-amplify-analytics-name",
provider: "AWSPinpoint",
});
}, [endTime]);
Here, I have used functions like Analytics.record
and Analytics.autoTrack
to track users with various parameters like sessions, pageview, events, and custom attributes like time spent on the page. See these docs for more details.
By integrating different analytics in the app, your pinpoint console will generate some analytics like this-
As you can see, we can track users on various parameters like daily or monthly active users.
Deploy your app automatically by just adding your git repository to the amplify console
Sign in to the AWS Management Console and open the Amplify service.
In the Amplify Console, click on the "Connect app" button.
Select your Git provider (e.g., GitHub, Bitbucket, GitLab) and follow the prompts to authorize Amplify's access to your repository.
Choose the repository and branch you want to deploy from.
Configure the build settings for your app. This includes selecting the framework or platform, specifying the build commands, and setting environment variables if needed.
Review the build settings and click on the "Next" button.
Configure the deployment settings. This includes specifying the branch to deploy from, the build settings, and the environment variables.
Review the deployment settings and click on the "Next" button.
Optionally, you can set up a custom domain for your app by following the instructions provided. This step is not mandatory.
Review the final settings and click on the "Save and Deploy" button.
After doing all these steps. your amplify project will look like this-
How to setup In-App messaging using AWS Pinpoint
This is the code that is present in the app dashboard:
const { InAppMessaging } = Notifications;
const [sendInAppMessage, setSendInAppMessage] = React.useState(false);
useEffect(() => {
Analytics.record(refreshEvent);
InAppMessaging.dispatchEvent(refreshEvent);
}, [sendInAppMessage]);
const refreshEvent = {
name: "refresh",
label: "Refresh",
page: "Dashboard",
handler: () => {
console.log("refresh");
},
};
useEffect(() => {
InAppMessaging.syncMessages();
}, []);
useEffect(() => {
const listener = InAppMessaging.onMessageReceived(myMessageReceivedHandler);
return () => listener.remove();
}, []);
useEffect(() => {
const displayListener = InAppMessaging.onMessageDisplayed(
myMessageDisplayedHandler
);
return () => displayListener.remove();
}, []);
const myMessageReceivedHandler = (message) => {
console.log(message);
};
const myMessageDisplayedHandler = (message) => {
console.log("Message displayed", message);
};
There are two ways of sending an in-app message using the notification service from Amplify.
The first way is to record an event first using
Analytics.record()
and then calling theInAppMessaging.dispatch()
function. It will trigger an event and then AWS pinpoint will send a message which is configured in your campaign. It is mandatory to keep a campaign running to listen to all the in-app-related messages.The second and simple way to create a campaign directly from the pinpoint console. The app had a campaign in the past, which used to trigger an in-app message notification on every login. You can see the campaign details below.
In the schedule section, you can see the trigger event is set to
_userauth_sign_in
, which triggers an in-app message to the app.The users will see a message as seen in the screenshot. You can easily customize your message in the pinpoint campaign.
Things to keep in mind while working with Amplify
Always do
amplify push
after making local changes: do this if you are adding something new or editing the amplify services by usingamplify add
oramplify update
.Familiarize yourself with the Amplify documentation: AWS Amplify has extensive documentation that provides detailed information about its features, services, and best practices. Make sure to read the documentation thoroughly to understand how to use Amplify effectively.
Understand the Amplify architecture: Amplify is built on top of various AWS services, such as AWS AppSync, AWS Lambda, and Amazon S3. It's crucial to have a good understanding of these underlying services to leverage Amplify's capabilities fully.
Plan your project structure: Before starting with Amplify, it's essential to plan your project structure. Decide on the services you want to use, such as authentication, storage, or API, and organize your project accordingly. This will help you maintain a clean and scalable codebase.
Use the Amplify CLI: The Amplify Command Line Interface (CLI) is a powerful tool that simplifies the process of setting up and managing your Amplify project. It allows you to create and configure services, deploy your application, and perform other essential tasks. Make sure to familiarize yourself with the Amplify CLI commands.
Conclusion
In this article, we explored the exciting journey of building a Shopify alternative app in just three weekends using AWS Amplify. We witnessed how Amplify's powerful features and integrations allowed us to rapidly develop an app. From the seamless login/signup process with AWS Cognito to the GraphQL API for efficient database manipulation, Amplify proved to be a game-changer.
We also delved into the Amplify UI library, which provided us with pre-built React components, saving us valuable development time. Amplify Studio further streamlined our backend tasks, enabling us to focus on delivering a seamless user experience.
Throughout this project, Amplify's integration with AWS services like S3 for storage and Pinpoint for analytics proved invaluable. We harnessed the power of AWS Amplify to build a robust, scalable, and user-friendly app.
Thanks for reading until the end
Thank you for joining me on this journey as we explored the exciting world of AWS Amplify and my first article on Hashnode. I hope you found inspiration and valuable insights from my experience building an app using Amplify.
If you enjoyed this article, I encourage you to explore more content on Hashnode, a vibrant community of developers and tech enthusiasts sharing their knowledge and experiences. You can find my article and many other insightful posts on the platform.
You can also make your blog. Signup here and start your blogging journey.
Additionally, if you're interested in building your projects with AWS Amplify, I highly recommend diving into the official Amplify documentation. It's a treasure trove of resources and guides to help you unleash the full potential of Amplify. You can read their docs here.
Remember, sharing your own experiences and insights is a great way to contribute to the developer community. Consider writing your articles on Hashnode and sharing your learnings with others. Together, we can continue to learn, grow, and inspire each other.
Once again, thank you for reading until the end. I hope this article has sparked your curiosity and empowered you to embark on your own Amplify projects. Happy coding!