Anne's Handmade is a small business owned by a retiree in her 70s. I built this site to help supplement her income and to gain some experience building ecommerce websites. This was a great opportunity to integrate a wide range of features into a single application. My ultimate goal was to create a simple to use website for both the customer and business owner.
Links
Back End
The application is built on top of an GraphQL server powered by Node and backed with a PostgreSQL database. GraphQL is a modern alternative to a traditional REST API and allows the client to query exactly what it needs in one request, from a single endpoint.
Apollo Server
Apollo Server turns a standard Express server into a GraphQL server. I designed a schema
using the GraphQL Query Language which acts like a blueprint for all the operations available. All the operations are then implemented in resolvers
.
- Simplified example of a
schema
type Mutation {
signin(email: String!, password: String): User
}
type User {
id: ID!
name: String!
email: String!
password: String!
}
- Example of the
signin
resolver
const bcrypt = require('bcryptjs')
const validateInput = require('../utils/validateInput')
const createCookie = require('../utils/createCookie')
const signToken = require('../utils/signToken')
module.exports = async (_, args, ctx, info) => {
// validate email, password
validateInput('signin', args)
// check if there is a user with that email
const user = await ctx.prisma.user({ email: args.email.toLowerCase() })
// if no user throw error
if (!user) throw new Error(`no user for email ${args.email}`)
// check if password is matches database
const valid = await bcrypt.compare(args.password, user.password)
// if no match throw error
if (!valid) throw new Error('invalid password')
// create token
const token = signToken(user.id)
// create cookie
createCookie(ctx.res, token)
// return user
return user
}
Prisma
Prisma acts as an ORM and allows the application to communicate with the database. Prisma is nice to work with because all the developer has to do is supply a datamodel it creates the entire data layer API. Prisma comes with a handy CLI tool that makes interacting with the database easy from the command line.
Playground
A benefit of GraphQL is the Playground feature. When the backend server is running, a GUI is available at /graphql
where Queries
and Mutations
can be ran against the database. This is great for making sure everything works the way it is intended while in development.
- A simple
Query
displaying a list of all products in the database.
The server has the ability to send email to users when they interact with the site. The most important event is when a user purchases a product. When this occurs the user is sent a Thank You note along with the invoice for their purchase. The email contains some branding, a summary of products purchased and links back to the site.
- Thank You email
Permissions
The API allows for powerful operations such as creating, updating and deleting products. I built a permission system that protects the application from these operations being performed by bad actors. The ADMIN
role comes with elevated privledges that allow the user to execute these powerful actions. The default role is USER
, and people that signup are automatically assigned this role. In fact, the only people that have the ADMIN
privledges are myself and the business owner.
This system works by checking the user's credentials which are sent inside a cookie with all requests. The cookie contains the user's ID
, which is used to look them up in the database. The role
property is then examined to see if it matches the requirements for the operation requested. If the requirement is not met an error is thrown and the operation is denied.
- Permissions can be tested in the Playground. This example illustrates attempting to delete a product without the role of
ADMIN
.
AWS S3
AWS S3 is used to store product images. Since the ADMIN
has the ability to create and delete products from the front end user interface I added the ability to upload and delete images from AWS S3. This is a nice feature that makes adding new products much more efficient. Without the integration the images would have to be added to S3 separately which would be a pain.
Google Sheets
I used Google Sheets, a spreadsheet, as the single source of truth for all of the product data. I was able to integrate the Google Sheets API with my backend via a database seeding script. This was useful in development because I could drop the database and easily re-seed all the products when I made changes.
Front End
The user interface is built with Nextjs, a React framework for building server-side rendered applications.
Images
As a side note, I took all the photos for this site myself. I used the trunk of my white Honda Accord as the backdrop. I went through a tedious process to prepare the images for use on the web. Each image had to be cleaned up and cropped to a square using Gimp. Then I used JPEG Optimizer to compress the images. Keeping the images small keeps overhead low by reducing the storage space needed and by shrinking the amount of data transferred. This is also good for performance. I was able to reduce the size of each image from ~400KB to ~30KB.
Shopping Cart
The Cart is a standard ecommerce feature where a user can place items they intend to purchase. A product can be added to the Cart by clicking the 🛒 icon from the search results page or by clicking the Add to Cart
button from the product page. The Cart itself can be toggled in and out of view by clicking Cart on the page header. The number of items currently in the cart is always displayed.
-
When the Cart is opened each item is listed as well as a price breakdown including item price, tax, subtotal and total.
Stripe Checkout
Stripe Checkout provides a simple form that can recieve all the input necessary to complete a sale. When the user submits the form, the data is tokenized and sent to back end which then reaches out to Stripe to approve the sale. Once approved the back end creates a record of the sale including the shipping address of the user and the id of the sale on Stripe.
- Stripe Checkout makes the purchase flow smooth and easy for the user.
Responsive Design
In today's landscape a website needs to look good on a wide variety of screen sizes from televisions to smart phones. This is accomplished through the use of Media Queries. An example of this can be seen in the Catalog component. On a large screen products are displayed in a four column layout, but when the screen is small the layout is changed to one column. Notice the header is also different on smaller screens.
- Catalog on 1360px wide screen.
- Catalog on 375px wide screen.
Apollo Client
Apollo Client acts as the bridge between the user interface and the back end GraphQL server and enables several key features. The products available at Anne's Handmade are classified into three categories - NECKLACE
, BRACELET
or EARRINGS
, as well as several bead types - RED_JASPER
, BLACK_ONYX
, etc. When the user is browsing the catalog these parameters are fed to Apollo Client via query parameters
in the page URL
. Once Apollo Client has these parameters is queries the database and returns the matching products.
- Catalog displaying products with a bead type of
RED_JASPER
.
Pagination
Pagination is another feature made possible by Apollo Client. This means only a fixed number of results are returned with each request. This prevents requests from becoming too big and lets the user focus on a small number of products at a time. The user can browse pages by clicking Next
and Previous
.
Search
The Search feature uses Downshift to give the user a preview of any item matching their search criteria. A drop down is displayed with the name of the product and a small thumbnail image. When an individual item is clicked the user is brought to that product's page.
- Searching for the term
buddha
fetches 6 matches.
Orders
- A user can easily pull up their entire purchasing history.
Admin Dashboard
Only users with the ADMIN
role can access the powerful Admin Dashboard. From this interface the ADMIN
can create, update and delete products, as well as view sales records and inventory. The interface is built with React Table.
- React Table in action.
- Viewing a sales record to get the user's shipping address and toggle the shipped status to true. A link is also available to pull up the sales record on Stripe.
- Viewing an inventory record to update a product property, add/remove an image and raise/lower the price.
CI Build System
Travis is used to make sure all tests are passing before new code is sent to production. When new commits are pushed to version control Travis is triggered and the application is built on their servers. If the build succeeds a suite of tests I wrote is run with Jest. Only when all of these tests pass are the commits pushed to version control and the new code deployed.
Deployment
Currently, Anne's Handmade is deployed to a Linux based server on Digital Ocean and runs really fast for only $5 per month. Granted this site isn't seeing the same amount of traffic as Amazon, but I was impressed with the affordability when compared to other eCommerce solutions. I am able to SSH or login remotely to install and configure all the necessary components of the live site.