Natalie Smith Frontend developer 👩🏽‍💻 Previously at @designcodeio.

Getting started with Vendure

8 min read 2264

Getting started with Vendure

What is Vendure?

Vendure is a headless ecommerce framework built with TypeScript and Node.js.

Vendure features

  • Products and variants
  • Stock management
  • Payment provider integrations
  • Shipping provider integrations

What does “headless” mean?

You might have heard the term “API first” or “headless”. To sum it up, it provides your content (blog posts, products, etc.) as data over an API. This provides a lot of flexibility because it allows you to use the technology stack of your choice. Compared to a traditional CMS (content management system) which would require you to install a software and make you use a certain subset of technologies.

A little bit about our tech stack of choice

In this tutorial, I’m going to be using Next.js, GraphQL, and Apollo Client. Here’s a little bit about each:

  • Next.js —  a React framework that offers zero config, static generation, server-side rendering, and file-system routing, to name a few
  • GraphQL —  a query language for an API. In the query, you describe the type of data you want and it returns just that. Nothing more. GraphQL offers a variety of features including being able to get multiple resources in a single request, a type system, and much more!
  • Apollo Client — a state management library that enables you to manage remote and local data with GraphQL. It allows us to fetch, cache, and modify our application while automatically updating our UI. Some of the features include declarative data fetching, being able to use modern React features (like Hooks), and it’s universally compatible

Getting started with Vendure

Requirements:

  • Node.js v10.13.0 or above
  • NPM v5.2 or above or Yarn v0.25 or above
  • If on Windows, make sure you have the window build tools installed like this:
    • npm install --global --production windows-build-tools
# NPM
npx @vendure/create name-of-your-app
# Yarn
yarn create @vendure my-app

This will run Vendure Create and it will ask you a couple of questions regarding your project. For this, I have chosen not to use TypeScript and I chose SQLite for the database. Vendure recommends that you use SQLite if you just want to test out Vendure because it doesn’t require any external requirements.

Running it locally

After the installation, cd (change directory) into your project and run the following command:

npm run start

After running that command, let’s head on over to http://localhost:3000/admin/ to log in as an admin and add/view our products.

vendure admin login page, form inputs for username and email

Login with the admin username and password that you created during installation. If you forgot what those were, open the application in a text editor and head into the src > vendure-config.js file and under authOptions, you should see your username and password.

Once logged in, you should see the dashboard. Now let’s add some products.

Creating our store

In the dashboard, under catalog, click on Products:

vendure dashboard with menu on left side, the menu item 'product' is circled in red

If you chose the option to populate with data then it should look something like this:

vendure products page populated with items

If not, then go ahead and click the button that says New Product and fill in the information.

Integrating Next.js & GraphQL with Vendure

First, let’s take a look at the data. To do that, head over to http://localhost:3000/shop-api.

This will bring up the GraphQL playground which is a place to view all the available queries and mutations.

vendure graphql data

Setting up Next.js

npx create-next-app
# or
yarn create next-app

After installation, when you try to run the command npm run dev, you will get an error in the terminal saying that “Port 3000 is already in use.”

Open your project in a text editor and head into the package.json file and under scripts > dev, add the following:

next -p 8080

Now back in the terminal, run the following:

npm run dev

Go to localhost:8080 and you should see this:

welcome page for next js, sign saying next.js

Setting up Apollo Client

Let’s install everything we need for GraphQL and Apollo:

npm i apollo-boost graphql react-apollo @apollo/react-hooks -S

We’re going to be using next-with-apollo which is a high order component for Next.js:

npm install next-with-apollo
# or with yarn:
yarn add next-with-apollo

In the root of your project, create a folder called lib and a file called apollo.js and add the following:

lib > apollo.js

import withApollo from 'next-with-apollo';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';

export default withApollo(
  ({ initialState }) => {
    return new ApolloClient({
      uri: '<http://localhost:3000/shop-api>',
      cache: new InMemoryCache().restore(initialState || {})
    });
  },
  {
    render: ({ Page, props }) => {
      return (
        <ApolloProvider client={props.apollo}>
          <Page {...props} />
        </ApolloProvider>
      );
    }
  }
);

Now, we need to wrap the whole app in Apollo’s high order component. In pages, head into _app.js and add the following:

import '../styles/globals.css';
import withApollo from '../lib/apollo';
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}
export default withApollo(MyApp);

With this, we can now query for all of our products. In the pages > index.js file, delete the contents, and add the following:

pages > index.js

import Head from 'next/head';
import styles from '../styles/Home.module.css';

import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';

const QUERY = gql`
  {
    products {
      items {
        slug
        description
        assets {
          source
        }
      }
    }
  }
`;

function Home() {
  const { loading, data } = useQuery(QUERY);
  console.log(data);
  return <div className={styles.container}>home</div>;
}

export default Home;

In your browser’s console, you should see something that looks like this:

api response listing product items

Adding CSS & building the store homepage

In this tutorial, I will be using Material-UI. Let’s go ahead and install that:

# NPM
npm install @material-ui/core
# Yarn
yarn add @material-ui/core

At the root of the project, create a folder called components and add a file calledProductCard.js.

This will allow us to use Material UI components and our own stylesheet to center everything.

Add the following:

import styles from '../styles/ProductCard.module.css';

import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';

This will control the CSS for the Material UI components. Here we are defining the maximum width and the height for the images in the card component:

const useStyles = makeStyles({
  root: {
    maxWidth: 345,
  },
  media: {
    height: 140,
  },
});

Here we‘re destructuring the prop called data that we passed in through the Home component in pages > index.js:

function ProductCard({ data }) {
  const classes = useStyles();
  const { items } = data.products;
  . . .
}

Here we are looping through our items array and creating our product card in which we are passing the image URL, product name, etc., to our Material UI components:

return (
    <section className={styles.container}>
      {items.map((item) => {
        const imgUrl = item.assets[0].source;

        return (
          <div key={item.slug}>
            <Card className={classes.root}>
              <CardActionArea>
                <CardMedia
                  className={classes.media}
                  image={imgUrl}
                  title={item.slug}
                />
                <CardContent>
                  <Typography gutterBottom variant='h5' component='h2'>
                    {item.name}
                  </Typography>
                  <Typography
                    variant='body2'
                    color='textSecondary'
                    component='p'
                  >
                    {item.description}
                  </Typography>
                </CardContent>
              </CardActionArea>
              <CardActions>
                <Button variant='outlined' color='primary'>
                  Add To Cart
                </Button>
                <Button variant='outlined' color='secondary'>
                  Details
                </Button>
              </CardActions>
            </Card>
          </div>
        );
      })}
    </section>
  );

export default ProductCard;

Now, let’s center our product card and make it responsive. In thestyles folder, create a file called ProductCard.module.css and add the following:

styles > ProductCard.module.css

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  grid-gap: 1rem;
}

If you save it, it should now look like this:

products page listing a laptop, a tablet, and mouse

Building our shopping cart/checkout process

A cool thing about GraphQL is that it allows us to not only query our data but also update our data. We can do this with something called mutations. In Apollo Client, we’re going to use the useMutation hook to update our data and get the loading state, error, and success state.

To use it, we provide the useMutation hook a GraphQL query and in turn, it returns two of the following:

  • A mutate function that you can call to execute the mutation
  • An object that reflects the current status of the mutation execution

If we head back to http://localhost:3000/shop-api and click on DOCS > Mutations, you can see all the available mutations you can use:

list of graphql mutations

To start, I’m going to take a look at the addItemToOrder mutation to start building out our checkout process.



In the GraphQL playground, open a new tab and paste in the following mutation:

mutation {
  addItemToOrder(productVariantId: 3 quantity: 1) {
    lines {
      productVariant {
        id
        name
      }
      unitPrice
      quantity
      totalPrice
    }
  }
}

This will add a product to being an active order. If we query for activeOrder, we should see the recently added product:

{
  activeOrder {
    // Details you want to see
  }
}

Now, let’s take a look at shipping and how we can use the setOrderShippingAddress mutation:

fragment ActiveShippingOrder on Order {
  id
  createdAt
  updatedAt
  code
  state
  active
}

mutation AddAddressToOrder($input: CreateAddressInput!) {
  setOrderShippingAddress(input: $input) {
    ...ActiveShippingOrder
  }
}

A fragment is like a function, it’s reusable logic. In ActiveShippingOrder, we’re defining what data we want back when we invoke the setOrderShippingAddress function. In the AddAddressToOrder mutation, we’re creating a query variable called input and passing that to our setOrderShippingAddress function. Then, in the query variables tab, I’m only passing in an object with the two required parameters. The input parameter only requires a street address and a country code. To check out the full list of parameters you view them at DOCS > setOrderShippingAddress > input.

query variables listing address

To see the results, you need to add a product to the active order state. To do this, you can open up a new tab in your GraphQL playground and write the addItemToOrder mutation then head back to your setOrderShippingAddress mutation and run it and you should see the following:

shipping address shown in graphql playground

If you didn’t add a product to the active order state, then when you try to run your setOrderShippingAddress mutation, it will return null.

Lastly, we’ll take a look at payment. For this, we will use the addPaymentToOrder mutation:

mutation {
 addPaymentToOrder(method: "", metadata: {})
}

Note:

  • method: — this field should correspond to the code property of a PaymentMethodHandler

  • metadata: — this field should contain arbitrary data passed to the specified PaymentMethodHandler‘s createPayment() method as the “metadata” argument. For example, it could contain an ID for the payment and other data generated by the payment provider

In Next.js, we can use the useMutation hook to apply our mutations. For example, let’s try adding a product. First, we define the mutation gql:

const ORDER_FRAGMENT = gql`
  fragment ActiveOrder on Order {
    id
    code
    state
    total
    currencyCode
    lines {
      id
      productVariant {
        id
        name
        currencyCode
      }
      unitPriceWithTax
      quantity
      totalPrice
      featuredAsset {
        id
        preview
      }
    }
  }
`;
const ADD_TO_CART = gql`
  mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!) {
    addItemToOrder(productVariantId: $productVariantId, quantity: $quantity) {
      ...ActiveOrder
    }
  }
  ${ORDER_FRAGMENT}
`;

Similar to the GraphQL playground, we’re using query variables to pass that into our addItemToOrder mutation and using the ActiveOrder fragment to define what data we want back.

In addition, we also need a query to get all the active orders so let’s define that:

const GET_ACTIVE_ORDER = gql`
  {
    activeOrder {
      ...ActiveOrder
    }
  }
  ${ORDER_FRAGMENT}
`;

Now, we pass both gql to our useMutation hook:

const [addItemToOrder] = useMutation(ADD_TO_CART, {
    update: (cache, mutationResult) => {
      const { activeOrder } = cache.readQuery({
        query: GET_ACTIVE_ORDER,
      });
      cache.writeQuery({
        query: GET_ACTIVE_ORDER,
        data: {
          activeOrder: mutationResult.data.addItemToOrder,
        },
      });
    },
  });

We want to be able to update our query so when we try to query the GET_ACTIVE_ORDER, it won’t return null. To do this, you can provide a second argument to the useMutation hook. The update property accepts a method with two parameters to get the cache and the results. We’re going to use a method on the cache called writeQuery that will add our results from mutationResults to our GET_ACTIVE_ORDER query.

Now, we can call the addItemToOrder function on a onClick handler. This could be used when a user clicks on our “add to cart” button. It will move our selected product to the active state and then we can query for all active orders.

To add a shipping address, it’s similar to the addItemToOrder process in which you pass the gql to the useMutation hook and then to the onSubmit handler. Then, you can pass the data from your form to your mutation function.

To add payment, similar to addItemToOrder process, you would use the addPaymentToOrder mutation and depending on how you want to handle payments, i.e if you want to use Stripe or another service, then you would configure that in the method parameter.

How to deploy

Essentially, a Vendure application is simply just a Node.js application and it can be deployed anywhere Node.js is supported.

The docs recommend this article if you want to run it on the server and use nginx as a reverse proxy to direct requests to your Vendure application.

Conclusion

In conclusion, Vendure offers another perspective on being the next modern headless GraphQL-based ecommerce framework. It’s decoupled and flexible which means it chooses to focus on developer productivity and ease of customization. Although it’s still in beta, Vendure offers a wide range of features out of the box such as guest checkout, built-in admin interface, integrations with payment and shipping providers, and much more. A perfect choice for any small business that needs to get an ecommerce site up and running.

LogRocket: See the technical and UX reasons for why users don’t complete a step in your ecommerce flow.

LogRocket is like a DVR for web and mobile apps and websites, recording literally everything that happens on your ecommerce app. Instead of guessing why users don’t convert, LogRocket proactively surfaces the root cause of issues that are preventing conversion in your funnel, such as JavaScript errors or dead clicks. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Start proactively monitoring your ecommerce apps — .

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Natalie Smith Frontend developer 👩🏽‍💻 Previously at @designcodeio.

5 Replies to “Getting started with Vendure”

  1. Hi Natalie, thanks for this details blog, i’m trying to get this working on my local dev-env, and it works well until the step where you describe the ProductCard.js, i must be doing wrong because I never see the product on screen, else than in the console, and since i get no error in the terminal, i tried some “things” but with no result, can you please give me the exact contents of the ProductCard.js file, so I get it work and can go over the rest of your post.

    Thanks in advance

    Greeting

    Ruud

  2. Hi i try to install vendure for 4 times, with npm, yarn, but i always receive this error: {“message”:”An error occurred when installing dependencies. Try running with `–log-level info` or `–log-level verbose` to diagnose.”,”command”:”yarnpkg add –exact –ignore-engines @vendure/core @vendure/email-plugin @vendure/asset-server-plugin @vendure/admin-ui-plugin better-sqlite3 –cwd /home/lucasfrutig/www/itechrp/my-app”,”context”:{}}

    With –log-level verbose, some errors occurs on install better-sqlite, i don´t know why

Leave a Reply