In this article, we will build a fullstack e-commerce application similar to Alibaba using modern technologies like GraphQL, React, and React Native. The app will have all the essential features of an online marketplace including products listings, categories, shopping cart, payments, and mobile applications.
Let's get started!
Setting Up the Backend with GraphQL
The backend will be built using GraphQL as our API layer. We'll use the Apollo Server framework to write our GraphQL server. Checkout: https://zipprr.com/alibaba-clone/
First, let's initialize a new Node.js project:
npm init
Next, install the dependencies:
npm installgraphqlapollo-server-expressmongoosebcryptjsonwebtoken
GraphQL Schema Design
For the data model, we'll define the main types in our schema - Product
, Category
, User
etc.
type Query {
products: [Product]
categories: [Category]
user: User
}
type Product {
id: ID!
name: String!
price: Float!
description: String
image: String
category: Category
}
type Category {
id: ID!
name: String!
products: [Product]
}
type User {
id: ID!
email: String!
name: String
purchases: [Order]
}
Resolvers
Next, we define resolvers to fetch data from the MongoDB database:
// product.js
const Product = mongoose.model('Product');
export default {
products: () => Product.find({}),
product: (_, {id}) => Product.findById(id)
}
Authentication
For authentication, we'll use JSON Web Tokens (JWT) stored in cookies.
// auth.js
import jwt from 'jsonwebtoken';
export const authenticate = async (_, {email, password}) => {
const user = await User.findOne({email});
if(!user || !bcrypt.compareSync(password, user.password)) {
throw new Error('Invalid credentials');
}
return {
token: jwt.sign({userId: user.id}, process.env.JWT_SECRET)
};
}
GraphiQL Testing
We can now test our API using GraphiQL. Start the server:
node index.js
Make queries on http://localhost:4000/graphql
:
{
products {
name
price
}
}
This completes the backend setup with GraphQL!
Building the Web App with React
Now let's build the frontend React app that will consume the GraphQL API.
Project Setup
npm init react-app client
cd client
Apollo Client
Install Apollo Client to connect React to our GraphQL backend:
npm install @apollo/client graphql
setup Apollo Client:
import {ApolloClient, InMemoryCache} from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
Components
We'll create core components like ProductList
, ProductDetails
, Cart
, etc.
ProductList.js
:
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
const PRODUCTS_QUERY = gql`
query ProductsQuery {
products {
name
price
image
}
}
`
export default function ProductList() {
const { loading, error, data } = useQuery(PRODUCTS_QUERY);
if(loading) return <p>Loading...</p>
if(error) return <p>Error!</p>
return (
<div>
{data.products.map(product => (
<ProductItem
key={product.id}
product={product}
/>
))}
</div>
)
}
Authentication
For authentication, we use the API endpoint defined earlier:
const SIGNIN_MUTATION = gql`
mutation SignInMutation($email: String!, $password: String!) {
authenticate(email: $email, password: $password) {
token
}
}
`
function SignIn() {
const [signIn] = useMutation(SIGNIN_MUTATION);
const onSubmit = async (data) => {
const response = await signIn({
variables: {
email: data.email,
password: data.password
}
});
localStorage.setItem('token', response.data.authenticate.token);
}
//..
}
Routing
Add route protection and navigation with React Router:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={
<RequireAuth>
<ProductList />
</RequireAuth>
}/>
<Route path="/account" element={
<RequireAuth>
<AccountPage />
</RequireAuth>
}/>
</Routes>
</BrowserRouter>
)
}
This completes the frontend web app built with React!
Mobile App with React Native
React Native Setup
To build the mobile apps, we'll use React Native:
npx react-native init Mobile
GraphQL Connection
Setup Apollo Client same as web:
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://192.168.1.100:4000/graphql',
cache: new InMemoryCache()
});
Components
Create reusable cross-platform components:
import { View, Text, Image } from 'react-native';
export default function ProductItem({product}) {
return (
<View>
<Image source={{uri: product.image}} />
<Text>{product.name}</Text>
<Text>${product.price}</Text>
</View>
)
}
Authentication
Sign In/Up screens with mutations:
function SignInScreen() {
const [signIn] = useMutation(SIGNIN_MUTATION);
const onSubmit = async ({email, password}) => {
try {
const {data} = await signIn({variables: {...}});
setToken(data.authenticate.token);
Navigation.replace('Home');
} catch (err) {
alert('Invalid credentials!');
}
}
//..
}
Navigation
Add react-navigation for app routing:
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen} />
<Stack.Screen
name="Products"
component={ProductListScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
This completes the mobile app development with React Native!
Hosting and Deployment
Deploying GraphQL Server
We can deploy the GraphQL server to Heroku :
heroku create
git push heroku main
Add environment variables:
heroku config:set JWT_SECRET=secret
React Web App
Build and deploy the React frontend to GitHub Pages:
npm run build
# in client directory
npm install gh-pages --save-dev
# edit package.json
"homepage": "https://USER.github.io/client",
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}
npm run deploy
React Native Apps
We can deploy the mobile builds to App Stores after testing:
For iOS, generate a signed archive and submit to App Store Connect.
For Android, create a signed release bundle and upload to Google Play Console.
Data Modeling with GraphQL
GraphQL's data modeling is more flexible compared to REST. Some advantages:
Schema as API
The GraphQL schema defines the full API surface area upfront. It clearly shows what can be queried or mutated without making requests.
Nested and Related Data
We can request nested and related data in a single query. For example, fetching a product with its category details:
{
product(id: "123") {
name
category {
name
}
}
}
Input Objects for Mutations
GraphQL uses input objects to define payloads for mutations. This avoids ambiguity and makes requests self-descriptive.
For example, the checkout mutation:
input CheckoutInput {
shippingAddress
paymentDetails
items: [ID!]
}
mutation Checkout($input: CheckoutInput!) {
checkout(input: $input) {
order {
id
}
}
}
Flexible Queries
Clients can request only the exact fields required via queries, avoiding over-fetching of data.
Authentication and Authorization
We implemented JWT token based authentication for our API. Here are some additional best practices:
Authorization with Permissions
We can define permissions levels in GraphQL like ADMIN
, USER
. Check these on resolvers:
export default {
mutateProduct: async (_, args, {user}) => {
if(!user.isAdmin) throw Error('Not authorized')
// mutate
}
}
Social Login
Add OAuth via social providers like Google/Facebook. Exchange OAuth tokens for JWT on backend.
export const socialLogin = async ({oauthToken, provider}) => {
// Find/create user from oauth info
return {token: signJwt(user)}
}
rateLimiting & blacklisting
Protect against brute force attacks by rate limiting requests and blacklisting IPs after failures.
HTTPS and Helmet
Always serve GraphQL over HTTPS. Use Helmet to set security headers.
This improves security of the authentication pipeline.
Payments Integration
For payments, we integrated with Stripe payment gateway:
Server:
- Install stripe pkg:
npm i stripe
- Define Stripe secret key:
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
- Charge credit card on checkout mutation:
constCharge = await stripe.charges.create({
amount,
currency,
source: token
});
// Save order
Client:
- Show Stripe elements on checkout page:
<StripeCardElement />
- Tokenize card on submit:
const {token} = await stripe.createToken({card});
- Pass token to server on mutation
We can also store payment methods and implement subscription payments.
Testing the Applications
Unit Testing
Write unit tests for React components using React Testing Library:
test('renders product data', () => {
const {getByText} = render(<ProductItem product={product}/>);
expect(getByText(product.name)).toBeInTheDocument();
})
Integration Testing
Test GraphQL resolvers and schemas:
test('gets a product by ID', async() => {
const product = await Product.get(id);
expect(product.name).toEqual('Product 1');
});
E2E Testing
Use Cypress for E2E flows like signup, purchase etc:
it('allows user to purchase a product', () => {
cy.visit('/products/1');
cy.contains('Add to Cart').click();
// Assertions
})
Growth and Maintenance
For continued growth:
Implement search and recommendations
Add admin dashboard
Marketplace seller subscriptions
Internationalization
Performance monitoring and caching
Email marketing and promotions
Follow best practices like code reviews, monitoring, logging and updating dependencies.
Conclusion
In this article, we built a fullstack e-commerce application similar to Alibaba using GraphQL, React and React Native. Key technologies used were - GraphQL, React, React Native, Apollo, MongoDB, Stripe etc. This demonstrated an end to end workflow for building production-ready fullstack applications.