Product
Resources
Ryan Belke
What we are building:
This tutorial is part of the ยซ Cooking a Deliveroo clone with Next.js (React), GraphQL, Strapi and Stripe ยป tutorial series.
Table of contents
Note: the source code is available on GitHub: https://github.com/strapi/strapi-examples/tree/master/nextjs-react-strapi-deliveroo-clone-tutorial.
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.
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:
restaurant
as name.Continue
and create the followings fields:name
with type Text.description
with type Rich Text.image
with type Media
.At this point, your server should have automatically restarted and a new link Restaurants
appears in the left menu.
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:
Add New Restaurant
.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
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:
Public
role.find
and findone
checkboxes of the Restaurant
section.
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.
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 } }
It looks you are going to the right direction. What if we would display these restaurants in our Next app?
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!.
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