In this article, we will develop a fullstack e-commerce application called Alibaba Clone that allows users to browse products, shop online and place orders from both web and mobile interfaces.
The app will be built using the GraphQL data query language on the backend, React for building the web UI and React Native for developing the mobile app. By sharing data layer and UI logic, we will see how GraphQL enables creation of unified and scalable fullstack applications.
Setting Up the Project
To get started, we will first initialize our project directories and install the necessary dependencies. We’ll create a root project folder called ‘alibaba-clone’ and inside it two sub-folders – ‘server’ for our GraphQL API and ‘client’ for the React frontend:
mkdir alibaba-clone
cd alibaba-clone
mkdir server client
Inside the server folder, we run npm init to initialize a package.json file:
cd server
npm init
We’ll install Apollo Server to build our GraphQL API:
npm install apollo-server graphql
Inside the client folder, we initialize a new React app using Create React App:
cd ../client
npx create-react-app .
This sets up the initial folder structure and dependencies we need to get started. Our GraphQL backend is now ready to define the schema, and our React app is setup to develop the frontend interfaces.
Creating the GraphQL Schema
With our dev environment initialized, let’s define the data model for our e-commerce store using GraphQL schema language. We’ll focus on key entity types like Products, Categories, Users etc.
We create a new file called schema.js
inside server folder and define the core Product type:
const { gql } = require('apollo-server');
const typeDefs = gql`
type Product {
id: ID!
name: String!
description: String
price: Float!
image: String
category: Category!
}
`
We add further types like Category, User etc. Products have a required name, price and belonging category. Users have profiles, carts etc.
This schema defines the basic data model and queries/mutations that can be performed. It acts as a contract between frontend and backend.
Setting Up the GraphQL Server
We instantiate Apollo Server and pass our GraphQL schema:
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const server = new ApolloServer({
typeDefs
});
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
By default it uses mock resolvers. We connect to MongoDB later using graphql-tools
and mongoose
.
Import and connect to database:
const mongoose = require('mongoose');
mongoose.connect(MONGO_URL)
.then(() => console.log('MongoDB Connected'))
.catch(err => console.log(err));
Define data models:
const productSchema = new mongoose.Schema({
name: String,
//...
});
const Product = mongoose.model('Product', productSchema);
Provide resolvers to fetch/modify data:
const resolvers = { Query: { products: () => Product.find({}) } }
Our GraphQL server is now configured to translate API requests into MongoDB queries!
Building the React Web App
We initialize Apollo Client inside src/index.js to enable GraphQL requests:
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { ApolloProvider, useQuery } from '@apollo/react-hooks';
const client = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
new HttpLink({ uri: 'http://localhost:4000/graphql' })
]),
cache: new InMemoryCache()
});
This allows us to connect to our GraphQL backend and perform queries in React components.
We create a Products component to fetch products data and display listings:
function Products() {
const { loading, error, data } = useQuery(GET_PRODUCTS);
if (loading) return <p>Loading...p>;
if (error) return <p>Error :(p>;
return data.products.map((product) => (
<div key={product.id}>
<img src={product.image} alt={product.name} />
<p>{product.name}p>
div>
));
}
This forms the basic product display functionality!
Creating React Components
We build out additional components like ProductDetails, Cart, CategoryFilters etc and integrate them in App.js.
For Product Details, we extract id from params and pass as variable:
function ProductDetails({ match }) {
const { id } = match.params;
const { loading, error, data } = useQuery(GET_PRODUCT, {
variables: { id }
});
// rest of component
}
For Cart, we maintain local state and integrate mutations:
function Cart() {
const [cart, setCart] = useState([]);
const addToCart = (product) => {
// add to cart
}
return (
<div>
{cart.map(item => (
<CartItem
key={item.id}
item={item}
removeFromCart={removeFromCart}
/>
))}
div>
)
}
This establishes the basic app layout and UI using React components.
Implementing Authentication
We integrate user authentication with JSON web tokens (JWT).
First install dependencies:
npm install jsonwebtoken bcryptjs express-jwt
Create User model and authentication functions:
// signup
const signup = async user => {
// hash password
// save user
// return token
}
// login
const login = async credentials => {
// validate
// return token
}
Protect routes with jwt middleware:
app.get('/profile', jwt({
secret: process.env.SECRET
}), (req, res) => {
res.json({ user: req.user })
})
Now only authenticated users can access routes like profile, cart etc. We add login/signup screens in React.
Adding Product CRUD
Only admins should modify products data. We create an Admin screen with form to:
Create products
(mutation with input fields)Update products
(populate form with EditProduct component)Delete products
(mutation to remove product)
We define mutations:
mutation CreateProduct($input: CreateProductInput) {
createProduct(input: $input) {
id
}
}
And call them on form submit with variables. Now admins fully manage product catalog.
Integrating Payments
To allow real transactions, we integrate Braintree payments SDK.
First install sdk:
npm install braintree
Create Dropin UI:
const clientToken = await fetchClientToken();
braintree.dropin.create({
authorization: clientToken
}, container);
On checkout:
const { nonce } = await braintree.dropin.requestPaymentMethod();
const result = await processPayment({
amount,
paymentMethodNonce: nonce
});
We save successful payments, generate orders. Now users can complete purchases through our site!
Building the Mobile App
We create a new React Native project:
npx react-native init Mobile
Import core dependencies:
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { ReactNativeApolloClient } from 'apollo-client-react-native';
Configure Apollo Client identical to web:
const client = new ReactNativeApolloClient({
uri: API_URL,
// other config
});
Now we can share queries, mutations and UI logic by extracting them into custom hooks and components.
We create basic screens like Home, Product, Cart etc and render web components on mobile. By sharing GraphQL implementation, web and mobile are now truly unified!
Styling the Mobile UI
To give our app a native look, we add platform-specific styling.
For iOS styling, we install and configure React Native Elements:
npm install react-native-elements
Import common components and styles:
import { Card, ListItem } from 'react-native-elements';
function HomeScreen() {
return (
<Card>
<ListItem title="iPhone Xs"/>
Card>
)
}
For Android styling, we use Material UI components:
import { Card, ListItem } from '@material-ui/core';
function configureTheme() {
//..
}
function HomeScreen() {
return (
<CardThemeProvider theme={theme}>
<Card>
<ListItem button>iPhone XsListItem>
Card>
CardThemeProvider>
)
}
We also customize colors, fonts globally using react-native-paper/primitives. This makes our app feel truly native on mobile platforms.
Adding Location Features
Mobile e-commerce apps commonly have location services. We integrate the following:
Geocoding: Convert addresses to lat/long using Google Maps API:
const location = await googleMaps.geocode({address: '1600 Amphitheatre Parkway, Mountain View, CA'});
```
Nearby Search: Find stores around user’s location
const stores = await googleMaps.nearbySearch({
location: userLocation,
radius: 1000
});
```
Dynamic Delivery Fees: Calculate delivery cost based on distance
const deliveryFee = getDeliveryFee(
userLocation,
storeLocation
);
```
Store Locations: Display stores on maps
latitude: store.latitude,
longitude: store.longitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
```
This greatly enhances user experience on mobile.
Testing and Deployment
No project is complete without testing and deployment. For testing, we setup:
Unit Tests: Test components in isolation using Jest
test('renders learn react link', () => {
//..
});
```
Integration Tests: Test API and UI interactions using Cypress
it('adds product to cart', () => {
// select product
// click add to cart
// assert cart updated
});
```
For continuous integration, we configure Travis CI to run tests on commits.
We deploy the GraphQL API to Heroku and React app to Netlify.
To ensure high availability, we use services like AWS for API hosting, Cloudfront CDN and DynamoDB.
Finally, proper error handling, analytics, caching are also important for productionization.
Conclusion and Future Work
In conclusion, we built a fully functioning e-commerce application with all major features – products, cart, payments, auth, admin – accessible through web and mobile interfaces thanks to GraphQL + React stack.
Some ideas for future work could be:
- Seller functionality – onboarding, inventory management
- Shipping/Logistics integrations
- Loyalty programs
- Order tracking/support
- Notifications, push, email marketing
- Related/recommended products
- Product reviews/ratings
- Checkout improvements – one click, saved details
- Internationalization – multiple languages
This demonstrates how GraphQL enables rapid development of complex Fullstack applications that can scale. I hope this article provided a good overview of the technical development process.