Build an uNFT marketplace with Next.js, Cosmic and Stripe integration
Tags: javascript, react, nextjs, webdev
Published: 2022-07-12 20:44:00

For this tutorial, I will show you how to build a marketplace app using Next.js, Cosmic, and Stripe. After we are done, you will be equipped with a foundation to build your own projects using these powerful tools. Let’s get started.

Bonus features include deploying to [Vercel](https://vercel.com/), dark and light mode and using [Lighthouse](https://www.searchmetrics.com/glossary/google-lighthouse/) to measure the performance of the website’s [core web vitals metrics](https://web.dev/vitals/), which dramatically increases the user's experience and [website visibility](https://nextjs.org/learn/seo/introduction-to-seo/webcrawlers) in SEO search results (this app's Lighthouse score displayed below). 

![Lighthouse](https://imgix.cosmicjs.com/76515530-0195-11ed-b7be-d956591ad437-lighthouse.png?w=1500&auto=format)
Guys, if you like it please [**upvote**](https://www.producthunt.com/posts/unft-marketplace) in [**Product Hunt**](https://www.producthunt.com/posts/unft-marketplace). 

## Tools we’ll be using

- [Next.js](https://nextjs.org/) - scalable and high-performance **React.js** framework for modern web development. Provides a large set of features, such as hybrid rendering, route prefetching, automatic image optimization, and internationalization, out of the box.
- [Cosmic](https://www.cosmicjs.com/) - fast, fully managed [headless CMS](https://www.cosmicjs.com/headless-cms) that enables us to quickly manage and create website content including UGC (user-generated content).
- [Stripe](https://stripe.com/) - payments infrastructure that provides API tools to receive one-time and subscription payments.

## TL;DR

[Check out the code](https://github.com/cosmicjs/unft-marketplace)
[View the live demo](https://unft-marketplace-cosmicjs.vercel.app/)
[Install the app template](https://www.cosmicjs.com/apps/unft-marketplace)
![uNFT](https://imgix.cosmicjs.com/d2327370-0195-11ed-b7be-d956591ad437-home-dark.gif?w=1000)

### First, we need to provide architecture and design

Designing is fundamentally about taking things apart, in such a way that they can be put back together. Separating things into things that can be composed that's what design is. — [Rich Hickey](https://www.infoq.com/presentations/Design-Composition-Performance/). 

### [MVC](https://www.freecodecamp.org/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/) stands for model-view-controller software design pattern

Here's what each of those components means:

- **Model**: The backend that contains all the data logic. [Cosmic](https://docs.cosmicjs.com/#use-cases) Object Types (**Multiple or Singleton**) help us create dynamic, scalable data structures and the essence of their transformations is independent of the outer world.
- **View (UI)**: The frontend or graphical user interface (GUI). For it, we use React, which is described as the “**V”** layer in the MVC.
- **Controller**: The brains of the application control how data is displayed. We use React’s Context API to decouple the state from the presentation **View** and facilitate business logic re-use.

![MVC3 image](https://imgix.cosmicjs.com/401559f0-0161-11ed-b7be-d956591ad437-MVC3.png?w=1500&auto=format)

MVC pattern is used for modern web applications because it allows the application to be scalable, maintainable, and easy to expand. 

## Setting up a project with Next.js

**Next.js** sets out to improve two things: **developer and user experiences** and is a complete toolkit to build blazing fast React apps. Next.js offers an abstraction that solves the most common, mundane, and complex tasks like routing, internalization, and image optimization. By default, Next.js pre-renders every page. This means that Next.js *generates HTML for each page in advance*, instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and [SEO](https://en.wikipedia.org/wiki/Search_engine_optimization).
Next.js has two forms of [pre-rendering](https://nextjs.org/learn/basics/data-fetching/two-forms): **[Static Generation](https://nextjs.org/docs/basic-features/pages#static-generation-recommended) (SSG)** and **[Server-side Rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering) (SSR)**. 

- **[Static Generation](https://nextjs.org/docs/basic-features/pages#static-generation-recommended)** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then *reused* on each request.
- **[Server-side Rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering)** is the pre-rendering method that generates the HTML on **each request**.

In our project, we use **[Server-side Rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering)** for dynamic data and pre-render pages that will always be up-to-date.
![server-side-rendering.png](https://imgix.cosmicjs.com/4007ec70-0161-11ed-b7be-d956591ad437-server-side-rendering.png?auto=format)

To get started, we'll create a [Next.js](https://nextjs.org/docs) app that includes tooling and configurations. For this tutorial, you'll need [Node.js](https://nodejs.org/en/) 12.22.0 or a later version. Open the terminal, paste or type

```
npx create-next-app@latest unft-marketplace
# or
yarn create next-app unft-marketplace
```

Change into the new directory and install the dependencies using a package manager like `npm`, `yarn`, or `pnpm` and then start the app in the development mode:

```
cd unft-marketplace
npm install cosmicjs stripe @stripe/stripe-js sass

npm run dev
# or
yarn dev
```
![uNFT](https://ph-files.imgix.net/cd6da5fc-a068-43bf-aa8c-e5d24a6e69c3.png?auto=format&auto=compress&codec=mozjpeg&cs=strip&fit=max&dpr=1)

To use the template UI you need to clone it on [GitHub](https://github.com/cosmicjs/unft-marketplace). Open the terminal, paste or type this code to install all dependencies, and run it. 

```
git clone https://github.com/cosmicjs/unft-marketplace
cd unft-marketplace

yarn install
yarn dev
```

Open [http://localhost:3000](http://localhost:3000/) in your browser to see the ascetic home page.

## Managing API keys/secrets with Next.js

When working with API keys and secrets, we need to make sure we keep them secret and out of version control, while conveniently making them available as `.env` variables. You'll need to create a .env file at the root of the project. Log in to [Cosmic](https://app.cosmicjs.com/) and from `Bucket Settings > API Access` takes the following values:

```jsx
NEXT_PUBLIC_COSMIC_BUCKET_SLUG=your_cosmic_slug
NEXT_PUBLIC_COSMIC_READ_KEY=your_cosmic_read_key
COSMIC_WRITE_KEY=your_cosmic_write_key

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=your_stripe_key
STRIPE_SECRET_KEY=your_stripe_secret_key
```

The `NEXT_PUBLIC_`prefix automatically exposes this variable to the browser. Next.js will insert the value for these into the publicly viewable source code at build/render time. Therefore make sure to not use this prefix for secret values! The same way log in to [Stripe](https://dashboard.stripe.com/login) and **for developers** section take your keys.

## Next.js API routes

Next.js [API Routes](https://nextjs.org/docs/api-routes/introduction) to create API endpoints inside the Next.js app. They will be deployed as **Serverless Functions** (also known as **Lambdas**). In our case, for filtering Cosmic content by parameters we’ll create a file called `filter.js` in `pages/api` directory that has the following format:

```jsx
//pages/api/filter
import Cosmic from 'cosmicjs';

const bucket = Cosmic().bucket({
// Set these values, found in Bucket > Settings after logging in at https://app.cosmicjs.com/login
  slug: '',
  read_key: '',
} )

export default async function filterHandler(req, res) {
  const { query: {min, max, color, category, search} } = req;

  let queryParam = {};

  if(typeof min !== 'undefined' || typeof max !== 'undefined') {
    queryParam = { ...queryParam, "metadata.price": {"$gte": typeof min !== 'undefined' ? Number(min) : 1, "$lte": typeof max !== 'undefined' ? Number(max) : 1000000000 },}
  }

  if(typeof color !== 'undefined') {
    queryParam = { ...queryParam, "metadata.color": color,}
  }

  if(typeof category !== 'undefined') {
    queryParam = { ...queryParam, "metadata.categories": category,}
  }

  if(typeof search !== 'undefined') {
    queryParam = { ...queryParam, "title": { "$regex": search, "$options": "i" },}
  }

  const params = {
    query: {
      ...queryParam,
      type: 'products',
    },
    props: 'title,slug,metadata,created_at',
  }

  try {
    const data = await bucket.getObjects(params);
    res.status(200).json(data);
  } catch (error) {
    res.status(404).json(error);
  }
}
```

Note that:

- `req` is an instance of [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage), plus some pre-built [middlewares](https://nextjs.org/docs/api-routes/api-middlewares).
- `res` is an instance of [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse), plus some [helper functions](https://nextjs.org/docs/api-routes/response-helpers).
  
![search](https://imgix.cosmicjs.com/bafb2f40-0162-11ed-b7be-d956591ad437-search.gif)
    

## Cosmic features overview and integration

**Cosmic** is a great headless CMS that enables us to model, manage, and store our content and media in a convenient hosted dashboard, then fetch our content using available API tools. The API provides search and filtering capabilities out of the box which makes building search and filter features in our app easy. **Cosmic provides full modern functionality and features for creating interactive, independent, and dynamic websites**. 

**Customizable API** - Build out the schema, models, and controllers for the API from the admin dashboard. Cosmic offers both a **REST and GraphQL** API for our convenience.
**Webhooks** - Call back anywhere you need, to get the functionality you want, out of the box.
[**Queries**](https://docs.cosmicjs.com/examples/basic-queries) - allow you to find the correct asset with your data by **searching, filtering, and sorting**. You can use some [advanced queries](https://docs.cosmicjs.com/examples/advanced-queries) to create more granular search capabilities.
**[Add Media and Object](https://docs.cosmicjs.com/examples/add-media)** - You can enable user generated content in your app using the Cosmic API. Users can upload and create interactive content within your defined data models. Cosmic is not only a fully-managed content management system, but the API can be used to create user-generated content experiences as well.

![start project](https://ph-files.imgix.net/3c6856d8-0f42-4a76-851b-e95f67afc2fa.png?auto=format&auto=compress&codec=mozjpeg&cs=strip&fit=max&dpr=1)

## Getting started with Cosmic

The first step is creating a [**free Cosmic account**] (https://app.cosmicjs.com/signup) into [Cosmic Dashboard] (https://app.cosmicjs.com/signup) and a new Project. Let's select the "Start from scratch" option.

![start project](https://imgix.cosmicjs.com/a37f4350-0163-11ed-b7be-d956591ad437-start-project.png?w=1500&auto=format)

[Cosmic](https://docs.cosmicjs.com/#use-cases) Object Types (**Multiple or Singleton**) help us create dynamic, scalable, reusable data structures and our content model. Creating model sections, easy from scratch, defines the "Metafields" in the "Content Model". For a reusable model used across multiple Objects choose **Multiple Object Type**. For our project, we will create a reusable **Products** Object Type **model** using **Multiple** Object Relationships with **Categories**. This will enable us to add or remove **Products** to **Categories** and enable searching, and filtering by category.

![object type](https://imgix.cosmicjs.com/a3713990-0163-11ed-b7be-d956591ad437-object-type.png?w=1500&auto=format)

After creating the content model for our Products, you can begin adding Products from the dashboard like this:

![object](https://imgix.cosmicjs.com/a3850fb0-0163-11ed-b7be-d956591ad437-object.png?w=1500&auto=format)

You can define other content models by creating Object Types following this schema design:

- Singleton for a unique model,
- Multiple for reusable models.

Using Cosmic [queries](https://docs.cosmicjs.com/examples/basic-queries), you can quickly find the specific content according to **search, filter, and sorting** criteria.

![search](https://imgix.cosmicjs.com/bafb2f40-0162-11ed-b7be-d956591ad437-search.gif?w=1500&auto=format)

## User-generated content

Using the Cosmic capabilities to **[Add Media and Objects](https://docs.cosmicjs.com/examples/add-media),** logged-in users can upload and create their own form-submitted content. For our use case, the user can create new **Products** to add to the marketplace for sale. 

![UGC](https://imgix.cosmicjs.com/8cf865c0-0164-11ed-b7be-d956591ad437-ugc.gif?w=1500&auto=format)

Uploading files and dynamic content to Next.js requires API Routes as we will need to store secret keys securely in server-side environment variables. Here is a brief overview of how to upload files with React and [Next.js API routes](https://nextjs.org/docs/api-routes/introduction) using `formidable`, `fs`, and the [Cosmic](https://docs.cosmicjs.com/examples/add-media) `bucket.addMedia` method.

```jsx
//api/upload.js
import Cosmic from 'cosmicjs';
import formidable from 'formidable';
import fs from "fs";

const bucket = Cosmic().bucket({
  slug: process.env.NEXT_PUBLIC_COSMIC_BUCKET_SLUG,
  read_key: process.env.NEXT_PUBLIC_COSMIC_READ_KEY,
  write_key: process.env.COSMIC_WRITE_KEY,
});

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function uploadHandler( req,res ) {
  const form = new formidable.IncomingForm({ multiple: false });

  try {
    form.parse( req, async ( err, fields, files ) => {
      if (err) return reject(err);
      const cosmicRes = await saveFile(files.file);
      res.status( 200 ).json(cosmicRes);
    } );

  } catch (error) {
    res.status(404).json(error.message)
  }
}

const saveFile = async ( file ) => {
  const filedata = fs.readFileSync( file?.filepath );
  const media_object = { originalname: file.originalFilename, buffer: filedata };

  try {
    // Add media to Cosmic Bucket
    const cosmic_res = await bucket.addMedia({
      media: media_object
    } );
    await fs.unlinkSync(file?.filepath);
    return cosmic_res;
  } catch (error) {
    console.log(error);
    return;
  }
};
```

## Stripe overview and integration

**[Stripe](https://stripe.com/docs/checkout/quickstart)** provides a way to capture payments in your application. Much like Cosmic has done all of the heavy lifting for content infrastructure, Stripe provides the ecommerce infrastructure and tools to build delightful payment experiences for your customers, irrespective of the service or product being sold.  

![Buy it](https://imgix.cosmicjs.com/c82f16c0-0164-11ed-b7be-d956591ad437-buy-it-now.gif)

Due to [PCI compliance requirements](https://stripe.com/docs/security), the Stripe.js library has to be loaded from Stripe's servers. This creates a challenge when working with server-side rendered apps, as the window object is not available on the server. To help us manage that complexity, Stripe provides a [loading wrapper](https://github.com/stripe/stripe-js) that allows importing Stripe.js like an ES module:

```jsx
//lib/getStripe
import { loadStripe } from '@stripe/stripe-js';

let stripePromise;

const getStripe = () => {
  if(!stripePromise) {
    stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
  }

  return stripePromise;
}

export default getStripe;
```

Stripe.js is loaded as a side effect of the `import '@stripe/stripe-js'` statement.

**Create the Stripe checkout session**

Add an endpoint on your server that creates a **[Checkout Session](https://stripe.com/docs/api/checkout/sessions)**. A Checkout Session controls what your customer sees on the payment page such as line items, the order amount and currency, and acceptable payment methods. Also, you can enable or disable payment methods directly in the **[Stripe Dashboard](https://dashboard.stripe.com/settings/payment_methods)**.

```jsx
//api/stripe
import Stripe from 'stripe';

const stripe = new Stripe( process.env.STRIPE_SECRET_KEY );

export default async function handler(req, res) {
  if (req.method === 'POST') {
    try {
      const params = {
        submit_type: 'pay',
        mode: 'payment',
        payment_method_types: ['card'],
        billing_address_collection: 'auto',
        shipping_options: [
          { shipping_rate: 'shr_1L4pafH6oGDppJjV9MrYC7z0' },
          { shipping_rate: 'shr_1L4pn4H6oGDppJjVBL7vPTk1' },
        ],
        line_items: req.body.map((item) => {
          const img = item.metadata.image.imgix_url;

          return {
            price_data: { 
              currency: 'usd',
              product_data: { 
                name: item.title,
                images: [img],
              },
              unit_amount: item.metadata.price * 100,
            },
            adjustable_quantity: {
              enabled:true,
              minimum: 1,
            },
            quantity: item.quantity
          }
        }),
        success_url: `${req.headers.origin}/`,
        cancel_url: `${req.headers.origin}/`,
      }

      // Create Checkout Sessions from body params.
      const session = await stripe.checkout.sessions.create(params);

      res.status(200).json(session);
    } catch (err) {
      res.status(err.statusCode || 500).json(err.message);
    }
  } else {
    res.setHeader('Allow', 'POST');
    res.status(405).end('Method Not Allowed');
  }
}
```

## Next steps 

Though this marketplace app is mainly a proof of concept to learn about these modern solutions, it is a good starter to build on. There is potential to add new features such as:
1. User account page to display orders
2. User profile page to show off purchased jpegs
3. Comment system to capture reviews and conversations
4. Bitcoin / Lightning option for making purchases

## Conclusion

I hope you enjoyed this tutorial exploring how to use powerful API-first tools to build a full-featured marketplace application. This dynamic, functional, customizable, and fully integrated uNFT marketplace, powered by **Next.js,** **Cosmic, and Stripe** can be customized for other types of businesses as well. Please feel free to [fork the code](https://github.com/cosmicjs/unft-marketplace), and use it how you prefer!