Strapi blog logo
  • Product

      Why Strapi?Content ArchitectureRoadmapIntegrationsTry live demo
      OverviewContent Types BuilderMedia LibraryRoles & Permissions
      Discover Strapi Enterprise EditionDiscover our partners
  • Pricing

  • Solutions

      Static WebsitesMobile ApplicationsCorporate websitesEditorial SitesEcommerce
      Delivery HeroL'EquipeSociete GeneralePixelDustBanco International
      Discover all our user stories
  • Community

      CommunityWrite for the communityWall of LoveStrapi Conf 2021
      SlackGitHubYoutubeCommunity Forum
      Meet the Strapi Community StarsDiscover the Strapi Showcase
  • Resources

      BlogStartersNewsroomSupport
      Strapi AcademyTutorialsVideos GuidesWebinars
      The Guide to Headless CMS Strapi Community Forum
  • Docs

      Getting StartedContent APIConfigurationInstallationDeploymentMigration
      Getting StartedContent ManagerContent-Types BuilderUsers, Roles & PermissionsPlugins
      Developer DocumentationStrapi User Guide

Looking for our logo ?

Logo Brand download
Download Logo Pack
See more Strapi assets
Strapi blog logo
  • Product

    Product

    • Why Strapi?
    • Content Architecture
    • Roadmap
    • Integrations
    • Try live demo

    Features

    • Overview
    • Content Types Builder
    • Media Library
    • Roles & Permissions
    • Discover Strapi Enterprise Edition
    • Discover our partners
    Features cover

    Unlock the full potential of content management

    See all features
    Strapi Enterprise Edition

    Discover the advanced features included in Strapi Enterprise Edition.

    Get Started
  • Pricing
  • Solutions

    Solutions

    • Static Websites
    • Mobile Applications
    • Corporate websites
    • Editorial Sites
    • Ecommerce

    Stories

    • Delivery Hero
    • L'Equipe
    • Societe Generale
    • PixelDust
    • Banco International
    • Discover all our user stories
    Delivery Hero team

    Delivery Hero manages their partner portal with Strapi.

    Read their story
    turn 10 studios website

    How 1minus1 delivered a creative website for Turn10 Studios 25% faster with Strapi

    Discover their story
  • Community

    Community

    • Community
    • Write for the community
    • Wall of Love
    • Strapi Conf 2021

    Resources

    • Slack
    • GitHub
    • Youtube
    • Community Forum
    • Meet the Strapi Community Stars
    • Discover the Strapi Showcase
    Strapi Conf

    The first Strapi Global User Conference.

    Register now
    Write for the community

    Contribute on educational content for the community

    Discover the program
  • Resources

    Resources

    • Blog
    • Starters
    • Newsroom
    • Support

    Learning

    • Strapi Academy
    • Tutorials
    • Videos Guides
    • Webinars
    • The Guide to Headless CMS
    • Strapi Community Forum
    Introducing Strapi Academy

    Everything you need to know to master Strapi.

    Go to the academy
    Strapi Repository on GitHub

    Get started with the Strapi repository

    Go to repository
  • Docs

    Developers

    • Getting Started
    • Content API
    • Configuration
    • Installation
    • Deployment
    • Migration

    User Guide

    • Getting Started
    • Content Manager
    • Content-Types Builder
    • Users, Roles & Permissions
    • Plugins
    • Developer Documentation
    • Strapi User Guide
    Install Strapi

    Install Strapi locally or wherever you need.

    Get Started
    Migration Guides Strapi

    Using a previous version of Strapi? Migrate to the latest.

    Read Guides
Get Started
Back to articles

Create an food ordering app with Strapi and Next.js 2/7

ordering-app
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email
Ryan Belke

Ryan Belke

What we are building: Restaurants list

This tutorial is part of the ยซ Cooking a Deliveroo clone with Next.js (React), GraphQL, Strapi and Stripe ยป tutorial series.

Table of contents

  • ๐Ÿ—๏ธ Setup (part 1)
  • ๐Ÿ  Restaurants (part 2) - current
  • ๐Ÿ” Dishes (part 3)
  • ๐Ÿ” Authentication (part 4)
  • ๐Ÿ›’ Shopping Cart (part 5)
  • ๐Ÿ’ต Order and Checkout (part 6)
  • ๐Ÿš€ Bonus: Deploy (part 7)

Note: the source code is available on GitHub: https://github.com/strapi/strapi-examples/tree/master/nextjs-react-strapi-deliveroo-clone-tutorial.

๐Ÿ  Restaurants list

First of all, we need to display the list of restaurants in our web app. Of course, this list is going to be managed through our API. So, we are going to start configuring it.

Define Content Type

A Content Type, also called a model, is a type of data. The Strapi API includes by default, the user Content Type. Right now we need restaurants, so our new Content Type is going to be, as you already guessed, restaurant (Content Types are always singular).

Here are the required steps:

  • Navigate to the Content Type Builder in the sidebar (http://localhost:1337/admin/plugins/content-type-builder).
  • Click on `+ Create new collection type.
  • Set restaurant as name.
  • Click on Continue and create the followings fields:
    • name with type Text.
    • description with type Rich Text.
    • image with type Media.
  • Click on Finish
  • Click on Save, the server should restart automatically.

Content Type Builder

At this point, your server should have automatically restarted and a new link Restaurants appears in the left menu.

Add some entries

Well done! You created your first Content Type. The next step is to add some restaurants in your database. To do so, click on "Restaurants" in the left menu (http://localhost:1337/admin/plugins/content-manager/restaurant?source=content-manager).

You are now in the Content Manager plugin: an auto-generated user interface which let you see and edit entries.

Let's create a restaurant:

  • Click on Add New Restaurant.
  • Give it a name, a description and drop an image.
  • Save it.

Create as many restaurants as you would like to see in your apps.

note: newer version of Strapi looks slightly different than the gif, but same fields and concept

Content Manager

Allow access

Having the items in database is great. Being able to request them from an API is even better. As you already know, Strapi's mission is to create API (I have got a super secret anecdote for you: its name is coming from Bootstrap your API ๐Ÿ˜ฎ).

When you were creating your restaurant Content Type, Strapi created on the backend, a few set of files located in api/restaurant.

These files include the logic to expose a fully customizable CRUD API. The find route is available at http://localhost:1337/restaurants. Try to visit this URL and will be surprised to be blocked by a 403 forbidden error. This is actually totally normal: Strapi APIs are secured by design.

Don't worry, making this route accessible is actually super intuitive:

  • Navigate to the Settings then Roles & Permissions.
  • Click on the Public role.
  • Tick the find and findone checkboxes of the Restaurant section.
  • Save.

authenticated Important: do the same thing for the authenticated role.

Now go back to http://localhost:1337/restaurants: at this point, you should be able to see your list of restaurants.

Users Permissions

Enabling GraphQL

By default, API's generated withย Strapi are REST endpoints. We can easily integrate the endpoints into GraphQL endpoints using the integrated GraphQL plugin.

Let's do that.

Install the GraphQL plugin from the Strapi marketplace.

Click "Marketplace" on your admin dashboard and select GraphQL and click Download.

That's it, you are done installing GraphQL. Simple Enough.

Make sure to restart your strapi server if it does not auto restart

Restart your server, go to http://localhost:1337/graphql and try this query:

{ restaurants { id name } }

Strapi GraphQL

Display restaurants

It looks you are going to the right direction. What if we would display these restaurants in our Next app?

Restaurants list

Install Apollo in the frontend of our application, navigate to the /frontend folder in a terminal window:

Again I am going to lock to the current versions at the time of writing for compatibility. You can choose to use the latest if you follow any upgrade documentation for your package

warning, next-apollo v4 breaks this implementation careful if using the latest

next-apollo v3.1.10 graphql v15.0.0 apollo-boost v0.4.7 @apollo/react-hooks v3.1.5 @apollo/react-ssr v3.1.5

$ cd frontend

$ yarn add next-apollo@3.1.10 graphql@15.0.0 apollo-boost@0.4.7 @apollo/react-hooks@3.1.5 @apollo/react-ssr@3.1.5

To connect our application with GraphQL we will use Apollo and the next-apollo implementation to wrap our components in a withData function to give them access to make GraphQL data queries.

There are a couple different approaches to implementing GraphQL into a Nextjs app. We are going to extract the Apollo logic into a lib folder and wrap our _app.js component with a function called withData to give access to Apollo client config inside all child components. This is what gives Apollo access to make GraphQL queries directly in our components as you will see!

This structure allows us to extract certain shared logic of our application into distinct locations for easy upgrades/modifications in the future.

Feel free to implement a different method as you seem fit, this is not dependent on Strapi. Any GraphQL implementation will work with Strapi

Example repo detailing the Apollo Next.js implementation: https://github.com/adamsoffer/next-apollo-example.

Create a lib directory in the root of the project:

$ mkdir lib

$ cd lib

$ touch apollo.js


Path: /frontend/lib/apollo.js

/* /lib/apollo.js */

import { HttpLink } from "apollo-link-http";
import { withData } from "next-apollo";

const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:1337"

const config = {
  link: new HttpLink({
    uri: `${API_URL}/graphql`, // Server URL (must be absolute)
  })
};
export default withData(config);

We will generate the list of Restaurants inside a RestaurantList file as:

$ cd ..

$ cd components

$ mkdir RestaurantList

$ cd RestaurantList

$touch index.js

Let's wrap _app.js in our withData call to give the components access to Apollo/GraphQL.

Path: /fontend/pages/_app.js

/* _app.js */
import React from "react";
import App from "next/app";
import Head from "next/head";
import Layout from "../components/Layout";
import withData from "../lib/apollo";
class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;
    return (
      <>
        <Head>
          <link
            rel="stylesheet"
            href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
            integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
            crossOrigin="anonymous"
          />
        </Head>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </>
    );
  }
}
export default withData(MyApp);

Path: /frontend/components/RestaurantList/index.js

/* components/RestaurantList/index.js */
import { useQuery } from "@apollo/react-hooks";
import { gql } from "apollo-boost";

import Link from "next/link";

import {
  Card,
  CardBody,
  CardImg,
  CardText,
  CardTitle,
  Row,
  Col,
} from "reactstrap";

const QUERY = gql`
  {
    restaurants {
      id
      name
      description
      image {
        url
      }
    }
  }
`;

function RestaurantList(props) {
  const { loading, error, data } = useQuery(QUERY);
  if (error) return "Error loading restaurants";
  //if restaurants are returned from the GraphQL query, run the filter query
  //and set equal to variable restaurantSearch
  if (loading) return <h1>Fetching</h1>;
  if (data.restaurants && data.restaurants.length) {
    //searchQuery
    const searchQuery = data.restaurants.filter((query) =>
      query.name.toLowerCase().includes(props.search)
    );
    if (searchQuery.length != 0) {
      return (
        <Row>
          {searchQuery.map((res) => (
            <Col xs="6" sm="4" key={res.id}>
              <Card style={{ margin: "0 0.5rem 20px 0.5rem" }}>
                <CardImg
                  top={true}
                  style={{ height: 250 }}
                  src={`${process.env.NEXT_PUBLIC_API_URL}${res.image[0].url}`}
                />
                <CardBody>
                  <CardTitle>{res.name}</CardTitle>
                  <CardText>{res.description}</CardText>
                </CardBody>
                <div className="card-footer">
                  <Link
                    as={`/restaurants/${res.id}`}
                    href={`/restaurants?id=${res.id}`}
                  >
                    <a className="btn btn-primary">View</a>
                  </Link>
                </div>
              </Card>
            </Col>
          ))}

          <style jsx global>
            {`
              a {
                color: white;
              }
              a:link {
                text-decoration: none;
                color: white;
              }
              a:hover {
                color: white;
              }
              .card-columns {
                column-count: 3;
              }
            `}
          </style>
        </Row>
      );
    } else {
      return <h1>No Restaurants Found</h1>;
    }
  }
  return <h5>Add Restaurants</h5>;
}
export default RestaurantList;

Now update your /pages/index.js home route to display the Restaurant list and a search bar to filter the restaurants:

Path: /frontend/pages/index.js

/* /pages/index.js */
import React, { useState } from "react";

import { Col, Input, InputGroup, InputGroupAddon, Row } from "reactstrap";
import RestaurantList from "../components/RestaurantList";

function Home() {
  const [query, updateQuery] = useState("");
  return (
    <div className="container-fluid">
      <Row>
        <Col>
          <div className="search">
            <InputGroup>
              <InputGroupAddon addonType="append"> Search </InputGroupAddon>
              <Input
                onChange={e => updateQuery(e.target.value.toLocaleLowerCase())}
                value={query}
              />
            </InputGroup>
          </div>
          <RestaurantList search={query} />
        </Col>
      </Row>
      <style jsx>
        {`
          .search {
            margin: 20px;
            width: 500px;
          }
        `}
      </style>
    </div>
  );
}
export default Home;

Now you should see the list of restaurants on the page that are filterable!.

Restaurants list

Well done!

๐Ÿ” In the next section, you will learn how to display the list of dishes: https://strapi.io/blog/nextjs-react-hooks-strapi-dishes-3

  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

Unleash content.

Read the docs
Get Started

Strapi is the most popular open-source Headless CMS. Strapi gives developers the freedom to use their favorite tools and frameworks while allowing editors to easily manage their content and distribute it anywhere.

Product

  • Why Strapi?
  • Content Architecture
  • Features
  • Enterprise Edition
  • Partner Program
  • Roadmap
  • Support
  • Integrations
  • Try live demo

Resources

  • How to get started
  • Meet the community
  • Tutorials
  • API documentation
  • GitHub repository
  • Starters
  • Strapi vs Wordpress
  • The Guide to headless CMS

Integrations

  • Gatsby CMS
  • React CMS
  • Vue.js CMS
  • Nuxt.js CMS
  • Next.js CMS
  • Angular CMS
  • Gridsome CMS
  • Jekyll CMS
  • 11ty CMS
  • Svelte CMS
  • Sapper CMS
  • Ruby CMS
  • Python CMS

Company

  • About us
  • Blog
  • Careers
  • Contact
  • Newsroom
  • ยฉ 2021, Strapi
  • LicenseTermsPrivacy