Introduction
This tutorial will guide you through building a restaurant finder application. This full-stack web application will allow users to search for restaurants based on location (zip code) and view a list of restaurants that match their criteria. We'll create the backend and frontend components and demonstrate how to connect them to create a fully functional restaurant finder.
This tutorial will include how to create a backend using Amplication, create a frontend using ReactJS, and connect the backend created by Amplication to the frontend.
Creating the backend:
In this part of the tutorial, we will use Amplication to develop the backend of our restaurant finder application. Amplication creates a fully functional production-ready backend with REST and GraphQL API, authentication, databases, best practices, etc., in a few minutes.
- Go to https://amplication.com.
- Login into it or make an account if you have not already.
- Create a new app and name it
restaurant-finder-backend.
- Click on
Add Resource
and then onService
and name itrestaurant-finder.
- Then, connect your GitHub account to the one on which you want Amplication to push the backend code and select or create a repository.
- Choose which API you want; we will go by the default, and that is both.
- Choose
monorepo
in this step. - Choose the database. We will go with
PostgreSQL
. - We will create entities from scratch.
- We want to have
auth
in the app. - Click on
create service,
and the code is generated now. - Go to Entities and click on
add entity
:- Entity Name:
Restaurant
- First field: name [Searchable, Required, Single Line Text]
- Second field: address [Searchable, Required, Single Line Text]
- Third field: phone [Searchable, Required, Single Line Text]
- Fourth field: zipCode [Searchable, Required, Single Line Text]
- Entity Name:
Click on Commit Changes and Build,
and in a few minutes, the code will be pushed to the GitHub repository. Go to the repo and approve the Pull Request that Amplication created.
Now, you have all the backend code generated by Amplication in your GitHub repository. It will look like this:
Backend Code: https://github.com/souravjain540/restaurant-finder
Now clone your repository, open it in the IDE of your choice, go to restaurant-finder-backend/apps/restaurant-finder,
and follow these commands:
npm install
npm run prisma:generate
npm run docker:dev
npm run db:init
npm run start
After all these steps, you will have a backend running on localhost:3000.
As Amplication comes with AdminUI and Swagger Documentation, you can go to localhost:3000/api
and view all the endpoints of the API that amplication generated.
Let’s click on the auth and make a POST
request by clicking on try it out with credentials admin
and admin.
Now click on execute, copy the accessToken,
and save it for the auth purpose later.
Now, we have our backend ready and running on our local system. It is now time to move to the frontend part.
NOTE: If you have any problems using Amplication while creating your web application or in the installation, please feel free to contact the Amplication Team on our Discord channel.
Creating the frontend:
In this part, we will build the frontend of our restaurant finder application using React, a popular JavaScript library for building user interfaces. We'll create the user interface for searching restaurants by zipcode, displaying search results, and adding new restaurants.
Setting up React Project:
First, ensure your system has Node.js and npm (Node Package Manager) installed. If not, you can download and install them from the official Node.js website.
Let's create a new React project using Create React App, a popular tool for setting up React applications with a predefined project structure. Open your terminal and run the following command:
npx create-react-app restaurant-finder
This command will create a new directory called restaurant-finder
containing all the necessary files and folders for your React project.
Designing the User Interface with Components
In React, you build user interfaces by creating components. Let's design the components for our restaurant finder application.
There will be three main components in our project:
-
SearchForm Component: This will be the main page of our project where the user will enter the
zipCode,
and all the restaurants with thatzipCode
will be shown as a list in the result. It will work in the root directory. (/
) -
RestaurantForm Component: This page will be responsible for adding any new restaurant to the list of restaurants. It will be a form with all the relevant details. It will work at
/restaurants/add.
-
RestaurantList Component: This page will show all the restaurants available in our database. It will work at
/restaurants.
Directory Structure:
To avoid any confusion, the file structure will look like this:
Create a SearchForm Component
Inside the src folder of your project, create a new file named SearchForm.js. This component will be responsible for the restaurant search form. Let's break down the code snippet of SearchForm.js
step by step:
-
Import Statements:
import React, { useState } from 'react'; import axios from 'axios';
- The code begins with importing the necessary modules.
React
is imported to define React components.useState
is a React hook used to manage component-level state.axios
is imported to make HTTP requests to the backend API.
-
getAuthToken
Function:const getAuthToken = () => { // Replace this with your logic to obtain the token return 'ebGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....'; // A placeholder token };
getAuthToken
is a function that should be replaced with your actual logic to obtain an authentication token.- In this code, it returns a placeholder token.
-
Axios Configuration:
const api = axios.create({ baseURL: 'http://localhost:3000/api', // Adjust the base URL to your API endpoint headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getAuthToken()}`, // Attach the token here }, });
api
is an instance of Axios configured with a base URL for the backend API.- It sets the
Content-Type
header to indicate that the request body is in JSON format. - It also attaches an authorization header with the token obtained from
getAuthToken
.
-
SearchForm
Component:function SearchForm({ onSearch }) { // ... }
SearchForm
is a React functional component that takes a prop namedonSearch
.- Inside this component, we will create the UI for searching restaurants by zip code.
-
Component State:
const [zipCode, setZipCode] = useState(''); const [restaurants, setRestaurants] = useState([]);
useState
is used to define two pieces of component state:zipCode
andrestaurants
.zipCode
stores the user's input for the zip code.restaurants
will store the search results.
-
handleSearch
Function:const handleSearch = () => { if (zipCode.trim() !== '') { // API request to search for restaurants based on the provided zip code // ... } };
handleSearch
is a function that is called when the user clicks the "Search" button.- It first checks if the
zipCode
is not empty. - If the
zipCode
is not empty, it makes an API request to search for restaurants based on the provided zip code.
-
Making the API Request:
api.get('/restaurants', { params: { where: { zipCode }, // Send the zipCode as a query parameter }, }) .then(response => { // Handle the API response // ... }) .catch(error => console.error('Error searching restaurants:', error));
- Axios is used to make a GET request to the
/restaurants
endpoint of the backend API. - It includes a query parameter
where
with the specifiedzipCode
. - If the request is successful, the response is processed in the
.then
block, and the search results are updated in therestaurants
state. - If there's an error, it is caught and logged in the
.catch
block.
- Axios is used to make a GET request to the
-
Rendering the UI:
- The component returns a JSX structure for rendering the search form, search button, and search results.
- The search results are displayed as a list of restaurants if there are any.
That's a breakdown of the SearchForm.js
code. It defines a React component for searching restaurants by zip code and requests API to retrieve restaurant data based on user input.
You can look into the final code of searchForm.js
: https://github.com/souravjain540/restaurant-finder-frontend/blob/main/src/components/searchForm.js
Create a RestaurantForm Component
Now, let's create a component for adding new restaurants. Create a file named RestaurantForm.js
inside the src folder. This component will allow users to input restaurant details and submit them to the backend. Let's break down the code snippet of RestaurantForm.js
step by step:
-
Import Statements:
import React, { useState } from 'react'; import axios from 'axios';
- The code begins with importing the necessary modules.
React
is imported to define React components.useState
is a React hook used to manage component-level state.axios
is imported to make HTTP requests to the backend API.
-
getAuthToken
Function:const getAuthToken = () => { // Replace this with your logic to obtain the token return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....'; // A placeholder token };
getAuthToken
is a function that should be replaced with your actual logic to obtain an authentication token.- In this code, it returns a placeholder token.
-
Axios Configuration:
const api = axios.create({ baseURL: 'http://localhost:3000/api', // Adjust the base URL to your API endpoint headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getAuthToken()}`, // Attach the token here }, });
api
is an instance of Axios configured with a base URL for the backend API.- It sets the
Content-Type
header to indicate that the request body is in JSON format. - It also attaches an authorization header with the token obtained from
getAuthToken
.
-
RestaurantForm
Component:function RestaurantForm({ onFormSubmit }) { // ... }
RestaurantForm
is a React functional component that takes a prop namedonFormSubmit
.- Inside this component, we will create the UI for adding a new restaurant.
-
Component State:
const [name, setName] = useState(''); const [address, setAddress] = useState(''); const [zipCode, setZipCode] = useState(''); const [phone, setPhone] = useState('');
useState
is used to define four pieces of component state:name
,address
,zipCode
, andphone
.- These states will store the user's input for the restaurant's name, address, zip code, and phone number.
-
handleFormSubmit
Function:const handleFormSubmit = (e) => { e.preventDefault(); const newRestaurant = { name, address, zipCode, phone, }; api.post('/restaurants', newRestaurant) .then(response => { // Call the onFormSubmit function with the newly created restaurant onFormSubmit(response.data); // Clear the form input fields setName(''); setAddress(''); setZipCode(''); setPhone(''); // Refresh the page after a successful submission window.location.reload(); }) .catch(error => console.error('Error creating restaurant:', error)); };
handleFormSubmit
is a function that is called when the user submits the restaurant form.- It first prevents the default form submission behavior.
- It creates a
newRestaurant
object with the values entered in the form fields. - It makes a POST request to the
/restaurants
endpoint of the backend API to create a new restaurant. - If the request is successful, it calls the
onFormSubmit
function with the newly created restaurant data. - It also clears the form input fields, and then refreshes the page to reflect the updated restaurant list.
- If there's an error, it is caught and logged.
-
Rendering the UI:
- The component returns a JSX structure for rendering the restaurant form.
- The form includes fields for entering the restaurant's name, address, zip code, and phone number.
- When the user submits the form, the
handleFormSubmit
function is called.
That's a breakdown of the RestaurantForm.js
code. It defines a React component for adding a new restaurant to the system, and it makes an API request to create the restaurant on form submission.
You can view the whole code here: https://github.com/souravjain540/restaurant-finder-frontend/blob/main/src/components/restaurantForm.js
Creating the restaurantLists component:
Next, create a file named RestaurantList.js
inside the src folder. This component will display the list of restaurants returned by the search. Let's break down the code snippet of RestaurantList.js
step by step:
-
Import Statements:
import React, { useState, useEffect } from 'react'; import axios from 'axios';
- The code begins with importing the necessary modules.
React
is imported to define React components.useState
anduseEffect
are React hooks used to manage component-level state and side effects.axios
is imported to make HTTP requests to the backend API.
-
getAuthToken
Function:const getAuthToken = () => { // Replace this with your logic to obtain the token return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....'; // A placeholder token };
getAuthToken
is a function that should be replaced with your actual logic to obtain an authentication token.- In this code, it returns a placeholder token.
-
Axios Configuration:
const api = axios.create({ baseURL: 'http://localhost:3000/api', // Adjust the base URL to your API endpoint headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getAuthToken()}`, // Attach the token here }, });
api
is an instance of Axios configured with a base URL for the backend API.- It sets the
Content-Type
header to indicate that the request body is in JSON format. - It also attaches an authorization header with the token obtained from
getAuthToken
.
-
RestaurantList
Component:function RestaurantList() { // ... }
RestaurantList
is a React functional component that displays a list of restaurants.- Inside this component, we will fetch the list of restaurants from the backend and display them.
-
Component State:
const [restaurants, setRestaurants] = useState([]);
useState
is used to define a state variablerestaurants
that will store the list of restaurants fetched from the API.
-
Fetching Restaurants:
useEffect(() => { api.get('/restaurants') .then(response => { setRestaurants(response.data); }) .catch(error => console.error('Error fetching restaurants:', error)); }, []);
- The
useEffect
hook is used to fetch the list of restaurants when the component mounts (i.e., when it first renders). - It makes a GET request to the
/restaurants
endpoint of the backend API. - When the response is received, it sets the
restaurants
state with the data.
- The
-
Deleting Restaurants:
const handleDelete = (restaurantId) => { api.delete(`/restaurants/${restaurantId}`) .then(() => { // Filter out the deleted restaurant setRestaurants(restaurants.filter(restaurant => restaurant.id !== restaurantId)); }) .catch(error => console.error('Error deleting restaurant:', error)); };
- The
handleDelete
function is called when a user clicks the "Delete" button next to a restaurant. - It makes a DELETE request to the
/restaurants/{restaurantId}
endpoint of the backend API to delete the restaurant. - After successful deletion, it updates the
restaurants
state by filtering out the deleted restaurant.
- The
-
Rendering the UI:
- The component returns a JSX structure for rendering the list of restaurants.
- It maps over the
restaurants
array and displays each restaurant's name, address, phone number, and zip code. - A "Delete" button is provided for each restaurant, which triggers the
handleDelete
function when clicked.
That's a breakdown of the RestaurantList.js
code. It defines a React component for displaying a list of restaurants fetched from the backend and provides the ability to delete restaurants.
Find the complete code snippet here: https://github.com/souravjain540/restaurant-finder-frontend/blob/main/src/components/restaurantList.js
Changing the App.js file:
Let's break down the code snippet of App.js
step by step:
-
Import Statements:
import React from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import RestaurantList from '/Users/sauravjain/projects/my-restaurant-app/src/components/restaurantList.js'; import RestaurantForm from '/Users/sauravjain/projects/my-restaurant-app/src/components/restaurantForm.js'; import SearchForm from '/Users/sauravjain/projects/my-restaurant-app/src/components/searchForm.js'; import '/Users/sauravjain/projects/my-restaurant-app/src/index.css'; // Import the CSS file
- The code begins with importing the necessary modules and components.
React
is imported to define React components.BrowserRouter
,Routes
, andRoute
are imported fromreact-router-dom
for defining and handling routes in the application.RestaurantList
,RestaurantForm
, andSearchForm
are imported as components from their respective file paths.- The CSS file is imported to apply styles to the application.
-
App
Component:function App() { // ... }
App
is a React functional component that serves as the main component for the application.- Inside this component, you define the routes, layout, and functionality of the app.
-
Event Handlers:
const handleSearch = (searchResults) => { // Handle the search results (e.g., update state) console.log('Search results:', searchResults); }; const handleFormSubmit = (newRestaurantData) => { // Update the state with the new restaurant data console.log('Restaurant data: ', newRestaurantData); };
- Two event handler functions,
handleSearch
andhandleFormSubmit
, are defined. These functions are used to handle data received from child components. handleSearch
is intended to handle search results data, andhandleFormSubmit
is intended to handle new restaurant data.
- Two event handler functions,
-
Router Setup:
return ( <Router> <div className="App"> <h1>Restaurant Finder</h1> <Routes> <Route path="/" element={<SearchForm onSearch={handleSearch} />} /> <Route path="/restaurants" element={<RestaurantList />} /> <Route path="/restaurants/add" element={<RestaurantForm onFormSubmit={handleFormSubmit} />} /> <Route path="/restaurants/edit/:id" element={<RestaurantForm />} /> </Routes> </div> <footer> {/* Footer content */} </footer> </Router> );
- The
Router
component is used to wrap the entire application, enabling client-side routing. - Inside the router, there is a
div
with the class name "App" that serves as the main container for the application. - The
h1
element displays the title "Restaurant Finder."
- The
-
Routes Configuration:
- Inside the
Routes
component, different routes are defined using theRoute
component fromreact-router-dom
. - The routes specify which components to render when certain URLs are accessed.
- Inside the
-
Route Paths and Components:
/
path is associated with theSearchForm
component. TheonSearch
prop is passed to it, allowing it to handle search results./restaurants
path is associated with theRestaurantList
component./restaurants/add
path is associated with theRestaurantForm
component. TheonFormSubmit
prop is passed to it to handle form submissions./restaurants/edit/:id
path is associated with theRestaurantForm
component, presumably for editing restaurant data.
-
Footer Section:
- Below the
Router
content, there is a footer section with links to the author's Twitter profile and a mention of "Backend Powered by Amplication."
- Below the
This is an overview of the App.js
code, which sets up the routing and components for your restaurant finder application. It defines how different components are rendered based on the URL paths and handles events with the defined event handler functions.
Have a look at the final code snippet of the App.js
file here: https://github.com/souravjain540/restaurant-finder-frontend/blob/main/src/App.js
In the end, I encourage everyone to create the frontend by themselves according to their creativity, but if you want to have it like mine, please copy the index.html and index.css file as well.
In the end, your app will look like this:
Root directory(searchForm):
restaurantLists.js:
restaurantForm.js
You can have a look at the complete frontend code here: https://github.com/souravjain540/restaurant-finder-frontend/tree/main
If you have any problem with any part of this tutorial, please feel free to contact me on my Twitter account. Thanks for giving it a read.