Я уверен, что большинство из вас уже более чем знакомы со стеком MERN. Наличие интерфейса React с серверной частью Node / Express, которая подключается к базе данных MongoDB. Что ж, я покажу вам, насколько легко подключиться к серверной части Node, которая использует базу данных PostgreSQL для сохранения данных. И в качестве бонуса я даже покажу вам, как подключиться к https://harperdb.io/, которая является платформой управления данными SQL / NoSQL. Он полностью индексирован, не дублирует данные и работает на любом устройстве от периферии до облака.

Я предполагаю, что у вас уже есть понимание JavaScript, Node и SQL, поскольку это руководство предназначено для быстрого введения.

Вы создадите приложение, которое выглядит как на изображении ниже.

Предварительные требования

  • Установлено приложение Insomnia или Postman API
  • NPM / узел установлен на вашем компьютере
  • PostgreSQL установлен и настроен

Создать базу данных PostgreSQL

В этом руководстве я буду использовать Valentina Studio в качестве графического интерфейса для управления локальной базой данных PostgreSQL, которую вы можете найти здесь https://www.valentina-db.com/en/valentina-studio-overview. инструмент, который вам нравится, вы даже можете использовать командную строку для взаимодействия с вашей базой данных, если хотите.

Сначала создайте базу данных с именем metacritic, а затем используйте SQL под изображениями, чтобы создать таблицу с именем movies.

CREATE TABLE movies (
    movie_id SERIAL PRIMARY KEY,
    movie_name VARCHAR(200) NOT NULL,
    img_url TEXT NOT NULL,
    release_year INT NOT NULL,
    summary TEXT NOT NULL,
    director VARCHAR(200) NOT NULL,
    genre VARCHAR(100) NOT NULL,
    rating VARCHAR(100) NOT NULL,
    movie_runtime INT NOT NULL,
    meta_score INT NOT NULL
)

Затем используйте SQL под изображением, чтобы добавить данные в таблицу фильмов.

INSERT INTO movies (movie_name, img_url, release_year, summary, director, genre, rating, movie_runtime, meta_score)
VALUES ('Casino Royale', 'https://static.metacritic.com/images/products/movies/9/08b5f3a45845fa3b6d1cb5f4978b5081-250h.jpg', 2006, 'After earning his license to kill James Bonds first 007 mission takes him to Madagascar where he is to spy on a terrorist. Not everything goes as planned and Bond decides to investigate independently of MI6.', 'Martin Campbell', 'Action', 'PG-13', 144, 80),('Tenet', 'https://static.metacritic.com/images/products/movies/7/a60818c40f69031bf30ca846444011e4-250h.jpg', 2020, 'Armed with only one word - Tenet - and fighting for the survival of the entire world the Protagonist (John David Washington) journeys through a twilight world of international espionage on a mission that will unfold in something beyond real time. Not time travel. Inversion.', 'Christopher Nolan', 'Action', 'PG-13', 150, 69),('Mulan', 'https://static.metacritic.com/images/products/movies/0/a496c3f832582876dc9b0d66197cab78-250h.jpg', 2020, 'When the Emperor of China issues a decree that one man per family must serve in the Imperial Army to defend the country from Northern invaders Hua Mulan the eldest daughter of an honored warrior steps in to take the place of her ailing father. Masquerading as a man Hua Jun she is tested every step of the way and must harness her inner-strength and embrace her true potential. It is an epic journey that will transform her into an honored warrior and earn her the respect of a grateful nation…and a proud father.', 'Niki Caro', 'Action', 'PG-13', 115, 67),('The Old Guard','https://static.metacritic.com/images/products/movies/7/b1db3c24db156b33c9fcfbbc199fcfcb-250h.jpg', 2020, 'Led by a warrior named Andy (Charlize Theron) a covert group of tight-knit mercenaries with a mysterious inability to die have fought to protect the mortal world for centuries. But when the team is recruited to take on an emergency mission and their extraordinary abilities are suddenly exposed it’s up to Andy and Nile (Kiki Layne) the newest soldier to join their ranks to help the group eliminate the threat of those who seek to replicate and monetize their power by any means necessary.', 'Gina Prince-Bythewood', 'Action', 'R', 125, 70),('Greyhound', 'https://static.metacritic.com/images/products/movies/4/499215874bac5acda666be3659bacf7e-250h.jpg', 2020, 'In the early days of WWII an international convoy of 37 Allied ships led by captain Ernest Krause (Tom Hanks) in his first command of a U.S. destroyer crosses the treacherous North Atlantic while hotly pursued by wolf packs of Nazi U-boats.', 'Aaron Schneider', 'Action', 'PG-13', 91, 64),('The New Mutants', 'https://static.metacritic.com/images/products/movies/4/8fcef9e9a93457f7a0fdf2a51cf30a0d-250h.jpg', 2020, 'In an isolated hospital young mutants are being held for psychiatric monitoring. When strange occurrences begin to take place both their new mutant abilities and their friendships will be tested as they battle to try and make it out alive.', 'Josh Boone', 'Action', 'PG-13', 94, 43),('I Used to Go Here', 'https://static.metacritic.com/images/products/movies/5/9456ab11b0bd3b457f32c6c58157bf95-250h.jpg', 2020, 'Following the lackluster launch of her debut novel 35-year-old writer Kate Conklin (Gillian Jacobs) receives an invitation from her former professor and old crush (Jemaine Clement) to speak at her alma mater. With her book tour canceled and her ego deflated Kate decides to take the trip wondering if returning to her old college as a published author might give her the morale boost she sorely needs. Instead she falls into a comical regression – from misadventures with eccentric twenty-year-olds to feelings of jealousy toward her former professor’s new favorite student. Striking the balance between bittersweet and hilarious Kate takes a journey through her past to redefine her future.', 'Kris Rey', 'Comedy', 'PG-13', 80, 68),('Hooking Up', 'https://static.metacritic.com/images/products/movies/0/fffb93ea39fcce7d65563163daa57c4c-250h.jpg', 2020, 'She (Brittany Snow) is an adventurous writer pumping out scandalous content for a lifestyle magazine. He (Sam Richardson) is a hopeless romantic who’s just been dumped by his high school sweetheart and given a medical diagnosis that’s left him shook. After a chance meeting the mismatched duo hit the road on a cross country trip to provide them both some much needed healing.', 'Nico Raineau', 'Drama', 'R', 104, 44),('Infamous', 'https://static.metacritic.com/images/products/movies/4/6da52f15b0fec577a53de1255cff6518-250h.jpg', 2020, 'Living in a small Florida town and working at a diner was never Arielles (Bella Thorne) dream life. Shes always wanted more. Fame. Popularity. Admiration. When she falls for a recently paroled young criminal named Dean she drags him back into a life of danger learning that posting their criminal exploits on social media is an easy way to viral fame. Obsessed with their rising number of followers they embark on a dangerous adventure together that leads to robbery cop chases and even murder. Heading to Hollywood the City of Stars they will realize what it takes to become famous and have to decide if this dangerous lifestyle is really worth it.', 'Joshua Caldwell', 'Drama', 'PG-13', 100, 40),('The LEGO Movie', 'https://static.metacritic.com/images/products/movies/7/55a09ad4264baf7d3e32b23a693d2307-250h.jpg', 2014, 'An ordinary LEGO minifigure, mistakenly thought to be the extraordinary MasterBuilder, is recruited to join a quest to stop an evil LEGO tyrant from gluing the universe together.', 'Christopher Miller and Phil Lord', 'Action', 'PG', 100, 83)

Запустите приведенный ниже SQL, чтобы просмотреть все данные в таблицах фильмов.

SELECT * FROM movies

Создание внутреннего сервера Node / Express

Сначала перейдите в такое место, как рабочий стол или папку, а затем используйте приведенный ниже код, чтобы настроить свой проект с помощью приложения терминала.

mkdir meta-movies-app
cd meta-movies-app
mkdir backend
cd backend
npm init -y
npm i express cors dotenv axios knex pg
touch index.js
touch .gitignore
touch .env

Откройте проект в редакторе кода, а затем создайте сервер Node в файле index.js.

const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.get('/', (req, res) => res.send('Home Route'));
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on port ${port}, http://localhost:${port}`));

Добавьте этот сценарий выполнения в свой package.json файл.

"scripts": {
        "start": "node index.js"
    },

Добавьте этот код в свой .gitignore файл в корневой папке.

.env
node_modules

Запустите приложение из внутренней папки и перейдите в окно браузера, чтобы увидеть домашнюю страницу.

npm run start

Подключиться к базе данных PostgreSQL

Добавьте имя вашей базы данных, имя пользователя и пароль, как в примере ниже, в ваш .env файл. Я считаю, что имя пользователя всегда postgres при локальной работе с базами данных postgres.

DATABASE_HOST="127.0.0.1"
DATABASE="metacritic"
DATABASE_USERNAME="postgres"
DATABASE_PASSWORD="yourdatabasepassword"

Теперь обновите файл index.js в корневой папке, используя приведенный ниже код.

const express = require('express');
const cors = require('cors');
const knex = require('knex');
require('dotenv').config();
const db = knex({
    client: 'pg',
    connection: {
        host: process.env.DATABASE_HOST,
        user: process.env.DATABASE_USERNAME,
        password: process.env.DATABASE_PASSWORD,
        database: process.env.DATABASE,
    },
});
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// CORS implemented so that we don't get errors when trying to access the server from a different server location
app.use(cors());
// GET: Fetch all movies from the database
app.get('/', (req, res) => {
    db.select('*')
        .from('movies')
        .then((data) => {
            console.log(data);
            res.json(data);
        })
        .catch((err) => {
            console.log(err);
        });
});
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on port ${port}, http://localhost:${port}`));

Перезагрузите сервер, перейдите в окно браузера и перезагрузите страницу. Вы должны увидеть данные в своей базе данных для таблиц фильмов, возвращаемых как json, и эти данные также записываются в окно вашего терминала.

Вы можете посмотреть документацию к пакету Knex.js, чтобы узнать больше о коде http://knexjs.org/

Реализация некоторых функций CRUD

Замените код в вашем index.js файле приведенным ниже кодом. Теперь можно создавать, читать обновления и удалять данные из базы данных. Перезагрузите сервер Node, чтобы увидеть изменения.

const express = require('express');
const cors = require('cors');
const knex = require('knex');
require('dotenv').config();
const db = knex({
    client: 'pg',
    connection: {
        host: process.env.DATABASE_HOST,
        user: process.env.DATABASE_USERNAME,
        password: process.env.DATABASE_PASSWORD,
        database: process.env.DATABASE,
    },
});
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// CORS implemented so that we don't get errors when trying to access the server from a different server location
app.use(cors());
// GET: Fetch all movies from the database
app.get('/', (req, res) => {
    db.select('*')
        .from('movies')
        .then((data) => {
            console.log(data);
            res.json(data);
        })
        .catch((err) => {
            console.log(err);
        });
});
// GET: Fetch movie by movieId from the database
app.get('/:movieId', (req, res) => {
    const movieId = req.params.movieId;
    db.select('*')
        .from('movies')
        .where('movie_id', '=', movieId)
        .then((data) => {
            console.log(data);
            res.json(data);
        })
        .catch((err) => {
            console.log(err);
        });
});
// POST: Create movies and add them to the database
app.post('/add-movie', (req, res) => {
    const { movieName, imgUrl, releaseYear, summary, director, genre, rating, movieRuntime, metaScore } = req.body;
    db('movies')
        .insert({
            movie_name: movieName,
            img_url: imgUrl,
            release_year: releaseYear,
            summary: summary,
            director: director,
            genre: genre,
            rating: rating,
            movie_runtime: movieRuntime,
            meta_score: metaScore,
        })
        .then(() => {
            console.log('Movie Added');
            return res.json({ msg: 'Movie Added' });
        })
        .catch((err) => {
            console.log(err);
        });
});
// DELETE: Delete movie by movieId from the database
app.delete('/delete-movie', (req, res) => {
    const movieId = req.body;
    const movieIdToDelete = Number(movieId.movieId);
    console.log(movieIdToDelete);
    db('movies')
        .where('movie_id', '=', movieIdToDelete)
        .del()
        .then(() => {
            console.log('Movie Deleted');
            return res.json({ msg: 'Movie Deleted' });
        })
        .catch((err) => {
            console.log(err);
        });
});
// PUT: Update movie by movieId from the database
app.put('/update-movie', (req, res) => {
    db('movies')
        .where('movie_id', '=', 1)
        .update({ movie_name: 'Goldeneye' })
        .then(() => {
            console.log('Movie Updated');
            return res.json({ msg: 'Movie Updated' });
        })
        .catch((err) => {
            console.log(err);
        });
});
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on port ${port}, http://localhost:${port}`));

Использование инструмента API для тестирования различных конечных точек

В этом руководстве я буду использовать приложение Insomnia API для выполнения различных запросов CRUD. Используйте снимки экрана в качестве примера, чтобы увидеть, как он работает на вашем компьютере.

GET: получить все фильмы из базы данных

Просто перейдите на http://127.0.0.1:5000/ и нажмите Отправить, чтобы увидеть все данные базы данных, возвращенные как json.

GET: получить фильм по movieId из базы данных

Просто перейдите на http://127.0.0.1:5000/1 и нажмите Отправить, чтобы увидеть фильм, соответствующий идентификатору, возвращенному как json. Он будет работать с любым идентификационным номером, пока он есть в базе данных.

POST: создавайте фильмы и добавляйте их в базу данных

Отправьте запрос POST на http://127.0.0.1:5000/add-movie с данными пары ключ-значение, как показано на снимке экрана в примере. Затем перейдите к маршруту Получить все фильмы, чтобы увидеть новую запись. В качестве альтернативы вы можете просто использовать графический интерфейс базы данных или интерфейс командной строки, чтобы увидеть новую запись в базе данных.

УДАЛИТЬ: удалить фильм по идентификатору фильма из базы данных

Отправьте запрос DELETE на маршрут http://127.0.0.1:5000/delete-movie, используя имя movieId. И в качестве значения используйте любой идентификатор, который есть в базе данных, чтобы удалить эту запись.

PUT: обновить фильм по идентификатору фильма из базы данных

Используйте свой инструмент API и отправьте запрос PUT на http://127.0.0.1:5000/update-movie, чтобы обновить запись в базе данных. Перейдите в конец файла index.js, чтобы увидеть код для маршрута ОБНОВЛЕНИЯ. Вы можете изменить SQL-запрос, чтобы обновить любое из полей в таблице, а затем все, что вам нужно сделать, это выбрать movie_id, чтобы обновить его запись. Вы можете увидеть код Javascript и SQL-запрос ниже.

Python

// PUT: Update movie by movieId from the database
app.put('/update-movie', (req, res) => {
    db('movies')
        .where('movie_id', '=', 1)
        .update({ movie_name: 'Goldeneye' })
        .then(() => {
            console.log('Movie Updated');
            return res.json({ msg: 'Movie Updated' });
        })
        .catch((err) => {
            console.log(err);
        });
});

SQL

UPDATE movies SET movie_name = 'Goldeneye'
WHERE movie_id = 1

Молодец, вы только что создали приложение Node, которое подключается к базе данных PostgreSQL. Следующий раздел будет о HarperDB.

Создать базу данных HarperDB

Сначала вам нужно создать учетную запись HarperDB, а затем создать базу данных. Я назвал свою базу данных кино. Создать и настроить базу данных HarperDB очень просто. Просто проследите за этим видео Обзор запуска HarperDB Cloud, и вы также можете посмотреть документацию по HarperDB с Node здесь https://docs.harperdb.io/.

Учетные данные для входа

Для подключения к HarperDB вам понадобится код авторизации. Сначала используйте инструмент API, чтобы отправить запрос GET на URL-адрес HarperDB с вашим именем пользователя и паролем. Вам необходимо использовать базовую аутентификацию. Затем используйте кнопку сгенерировать код и выберите Node.js и HTTP, вы найдете свой код авторизации в коде заголовков. На изображениях ниже показано, как это делается.

Подключение к HarperDB

После настройки убедитесь, что вы обновили свой .env файл учетными данными HarperDB, как показано ниже.

DATABASE_HOST="127.0.0.1"
DATABASE="metacritic"
DATABASE_USERNAME="postgres"
DATABASE_PASSWORD="yourdatabasepassword"
HARPERDB_URL="https://yourdatabase.harperdbcloud.com/"
HARPERDB_USERNAME="admin"
HARPERDB_PASSWORD="yourpassword"
HARPERDB_AUTH="yourauthcode"

Теперь обновите ваш index.js файл, используя приведенный ниже код. Мы импортировали HarperDB, учетные данные для базы данных, а также создали маршруты, которые вы можете найти внизу с полными CRUD-запросами. Axios используется для получения данных из HarperDB API.

const express = require('express');
const cors = require('cors');
const knex = require('knex');
require('dotenv').config();
const axios = require('axios');
const db = knex({
    client: 'pg',
    connection: {
        host: process.env.DATABASE_HOST,
        user: process.env.DATABASE_USERNAME,
        password: process.env.DATABASE_PASSWORD,
        database: process.env.DATABASE,
    },
});
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// CORS implemented so that we don't get errors when trying to access the server from a different server location
app.use(cors());
// GET: Fetch all movies from the database
app.get('/', (req, res) => {
    db.select('*')
        .from('movies')
        .then((data) => {
            console.log(data);
            res.json(data);
        })
        .catch((err) => {
            console.log(err);
        });
});
// GET: Fetch movie by movieId from the database
app.get('/:movieId', (req, res) => {
    const movieId = req.params.movieId;
    db.select('*')
        .from('movies')
        .where('movie_id', '=', movieId)
        .then((data) => {
            console.log(data);
            res.json(data);
        })
        .catch((err) => {
            console.log(err);
        });
});
// POST: Create movies and add them to the database
app.post('/add-movie', (req, res) => {
    const { movieName, imgUrl, releaseYear, summary, director, genre, rating, movieRuntime, metaScore } = req.body;
    db('movies')
        .insert({
            movie_name: movieName,
            img_url: imgUrl,
            release_year: releaseYear,
            summary: summary,
            director: director,
            genre: genre,
            rating: rating,
            movie_runtime: movieRuntime,
            meta_score: metaScore,
        })
        .then(() => {
            console.log('Movie Added');
            return res.json({ msg: 'Movie Added' });
        })
        .catch((err) => {
            console.log(err);
        });
});
// DELETE: Delete movie by movieId from the database
app.delete('/delete-movie', (req, res) => {
    const movieId = req.body;
    const movieIdToDelete = Number(movieId.movieId);
    console.log(movieIdToDelete);
    db('movies')
        .where('movie_id', '=', movieIdToDelete)
        .del()
        .then(() => {
            console.log('Movie Deleted');
            return res.json({ msg: 'Movie Deleted' });
        })
        .catch((err) => {
            console.log(err);
        });
});
// PUT: Update movie by movieId from the database
app.put('/update-movie', (req, res) => {
    db('movies')
        .where('movie_id', '=', 1)
        .update({ movie_name: 'Goldeneye' })
        .then(() => {
            console.log('Movie Updated');
            return res.json({ msg: 'Movie Updated' });
        })
        .catch((err) => {
            console.log(err);
        });
});
// HarperDB Database routes
// GET: Fetch all movies from the database
app.get('/online/harperdb', (req, res) => {
    const data = { operation: 'sql', sql: 'SELECT * FROM dev.movies' };
    const config = {
        method: 'post',
        url: process.env.HARPERDB_URL,
        headers: {
            Authorization: `Basic ${process.env.HARPERDB_AUTH}`,
            'Content-Type': 'application/json',
        },
        data: data,
    };
    axios(config)
        .then((response) => {
            const data = response.data;
            console.log(data);
            res.json(data);
        })
        .catch((error) => {
            console.log(error);
        });
});
// GET: Fetch movie by movieId from the database
app.get('/online/harperdb/:movieId', (req, res) => {
    const movieId = req.params.movieId;
    console.log(movieId);
    const data = { operation: 'sql', sql: `SELECT * FROM dev.movies WHERE id = ${movieId}` };
    const config = {
        method: 'post',
        url: process.env.HARPERDB_URL,
        headers: {
            Authorization: `Basic ${process.env.HARPERDB_AUTH}`,
            'Content-Type': 'application/json',
        },
        data: data,
    };
    axios(config)
        .then((response) => {
            const data = response.data;
            console.log(data);
            res.json(data);
        })
        .catch((error) => {
            console.log(error);
        });
});
// POST: Create movies and add them to the database
app.post('/online/harperdb/add-movie', (req, res) => {
    const { movieName, imgUrl, releaseYear, summary, director, genre, rating, movieRuntime, metaScore } = req.body;
    console.log(req.body);
    const data = {
        operation: 'insert',
        schema: 'dev',
        table: 'movies',
        records: [
            {
                movie_name: movieName,
                img_url: imgUrl,
                release_year: releaseYear,
                summary: summary,
                director: director,
                genre: genre,
                rating: rating,
                movie_runtime: movieRuntime,
                meta_score: metaScore,
            },
        ],
    };
    const config = {
        method: 'post',
        url: process.env.HARPERDB_URL,
        headers: {
            Authorization: `Basic ${process.env.HARPERDB_AUTH}`,
            'Content-Type': 'application/json',
        },
        data: data,
    };
    axios(config)
        .then((response) => {
            const data = response.data;
            console.log(data);
            res.json(data);
        })
        .catch((error) => {
            console.log(error);
        });
});
// DELETE: Delete movie by movieId from the database
app.delete('/online/harperdb/delete-movie', (req, res) => {
    const movieId = req.body.movieId;
    console.log(movieId);
    const data = { operation: 'sql', sql: `DELETE FROM dev.movies WHERE id = ${movieId}` };
    const config = {
        method: 'post',
        url: process.env.HARPERDB_URL,
        headers: {
            Authorization: `Basic ${process.env.HARPERDB_AUTH}`,
            'Content-Type': 'application/json',
        },
        data: data,
    };
    axios(config)
        .then((response) => {
            res.send({ msg: 'Movie Deleted' });
            console.log('Movie Deleted');
        })
        .catch((error) => {
            console.log(error);
        });
});
// PUT: Update movie by movieId from the database
app.put('/online/harperdb/update-movie', (req, res) => {
    const movieId = req.body.movieId;
    console.log(movieId);
    const data = { operation: 'sql', sql: `UPDATE dev.movies SET movie_name = 'Goldeneye' WHERE id = ${movieId}` };
    const config = {
        method: 'post',
        url: process.env.HARPERDB_URL,
        headers: {
            Authorization: `Basic ${process.env.HARPERDB_AUTH}`,
            'Content-Type': 'application/json',
        },
        data: data,
    };
    axios(config)
        .then((response) => {
            res.send({ msg: 'Movie Updated' });
            console.log('Movie Updated');
        })
        .catch((error) => {
            console.log(error);
        });
});
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on port ${port}, http://localhost:${port}`));

Используйте свой инструмент API или просмотрите маршруты в браузере, чтобы увидеть данные, возвращенные как json из экземпляра базы данных HarperDB. Для маршрута обновления просто используйте свой инструмент API с парой ключ-значение, как показано ниже.

HarperDB хранит идентификаторы в виде строк, поэтому имейте в виду, что вы не сможете получать, обновлять и удалять фильм с помощью movieId, если его идентификатор является числом, если вы не внесете в код некоторые изменения. Мы сохраняем наши идентификаторы в виде чисел, однако между ними легко переключаться, просто сделав movieId строку вместо числа.

Вам нужно будет перезапустить сервер узла, чтобы увидеть изменения.

Создание интерфейса

Пришло время создать интерфейс, который будет получать данные из API. cd в корневую папку приложения meta-movies-app, а затем выполните приведенную ниже команду, чтобы настроить проект в React.

npx create-react-app frontend
cd frontend

Теперь запустите сервер приложения реакции, используя npm start или yarn start

Перейдите в свой проект реакции, а затем удалите все CSS внутри файла index.css. Затем замените код в файлах App.css и App.js приведенным ниже кодом.

App.css

@import url('https://fonts.googleapis.com/css2?family=Arsenal:wght@400;700&display=swap');
* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}
html {
    font-size: 62.5%;
}
body {
    font-size: 1.6rem;
    font-family: 'Arsenal', sans-serif;
    /* letter-spacing: 0.2rem; */
    background: rgb(242, 242, 242);
    color: #0e0e0e;
}
header {
    background: #0e0e0e;
    padding: 1rem;
}
header h1 {
    margin: 0 auto;
    text-align: center;
    text-transform: uppercase;
    color: #ffffff;
}
section {
    display: flex;
    flex-flow: row wrap;
    justify-content: space-evenly;
    margin: 4rem;
}
.form-container {
    margin: 2rem auto;
    width: 50rem;
    max-width: 100%;
    padding: 0 2rem 0 2rem;
}
form {
    display: flex;
    flex-flow: column;
}
form input {
    height: 3rem;
    padding: 1.5rem;
}
form textarea {
    padding: 1.5rem;
}
form button {
    padding: 1rem;
    border: none;
    background: #fcee0b;
    font-weight: bold;
    cursor: pointer;
    transition: background 0.3s;
    text-transform: uppercase;
}
form button:hover {
    background: rgb(243, 212, 35);
}
form div {
    display: flex;
    flex-flow: column;
    margin-bottom: 1.3rem;
}
.movie-container {
    background: #fcee0b;
    padding: 4rem;
    margin-top: 2rem;
    border-radius: 2rem 7rem;
    width: 50rem;
    max-width: 100%;
}
.movie-container h1 {
    font-size: 3rem;
}
.movie-container p {
    margin: 1rem 0 1rem 0;
    font-size: 2rem;
}
.movie-container img {
    width: 10rem;
    height: 15rem;
}
.high {
    background: #66cc32;
    width: 4rem;
    color: #ffffff;
    text-align: center;
    font-weight: 700;
    display: inline-block;
    padding: 0.5rem;
    border-radius: 1rem;
}
.medium {
    background: #ffcc32;
    width: 4rem;
    color: #ffffff;
    text-align: center;
    font-weight: 700;
    display: inline-block;
    padding: 0.5rem;
    border-radius: 1rem;
}
.low {
    background: #ff0100;
    width: 4rem;
    color: #ffffff;
    text-align: center;
    font-weight: 700;
    display: inline-block;
    padding: 0.5rem;
    border-radius: 1rem;
}
@media screen and (max-width: 1094px) {
    section {
        justify-content: center;
        /* margin: 0 auto; */
    }
}

App.js

import React, { Fragment, useState, useEffect } from 'react';
import './App.css';
const App = () => {
    useEffect(() => {
        const getAPI = () => {
            // Change this endpoint to whatever local or online address you have
            // Local PostgreSQL Database
            const API = 'http://127.0.0.1:5000/';
            fetch(API)
                .then((response) => {
                    console.log(response);
                    return response.json();
                })
                .then((data) => {
                    console.log(data);
                    setLoading(false);
                    setApiData(data);
                });
        };
        getAPI();
    }, []);
    const [apiData, setApiData] = useState([]);
    const [loading, setLoading] = useState(true);
    return (
        <Fragment>
            <header>
                <h1>Meta Movie Reviews</h1>
            </header>
            <div className="form-container">
                <h2>Add Movie</h2>
                <form method="POST" action="http://127.0.0.1:5000/add-movie">
                    <div>
                        <label>Movie Name</label>
                        <input type="text" name="movieName" required />
                    </div>
                    <div>
                        <label>Box Image</label>
                        <input type="text" name="imgUrl" required />
                    </div>
                    <div>
                        <label>Realease Year</label>
                        <input type="text" name="releaseYear" required />
                    </div>
                    <div>
                        <label>Summary</label>
                        <textarea rows="5" cols="50" name="summary"></textarea>
                    </div>
                    <div>
                        <label>Director</label>
                        <input type="text" name="director" required />
                    </div>
                    <div>
                        <label>Genre</label>
                        <input type="text" name="genre" required />
                    </div>
                    <div>
                        <label>Rating</label>
                        <input type="text" name="rating" required />
                    </div>
                    <div>
                        <label>Runtime</label>
                        <input type="text" name="movieRuntime" required />
                    </div>
                    <div>
                        <label>Meta Score</label>
                        <input type="text" name="metaScore" required />
                    </div>
                    <div>
                        <button type="submit">Add Movie</button>
                    </div>
                </form>
            </div>
            <main>
                {loading === true ? (
                    <div>
                        <h1>Loading...</h1>
                    </div>
                ) : (
                    <section>
                        {apiData.map((movie) => {
                            let metaColor = 'low';
                            if (movie.meta_score >= 70) {
                                metaColor = 'high';
                            } else if (movie.meta_score <= 69 && movie.meta_score >= 49) {
                                metaColor = 'medium';
                            } else {
                                metaColor = 'low';
                            }
                            return (
                                <div className="movie-container" key={String(movie.movie_id)}>
                                    <h1>{movie.movie_name}</h1>
                                    <p>
                                        <strong>Director:</strong> {movie.director}
                                    </p>
                                    <p>
                                        <strong>Genre:</strong> {movie.genre}
                                    </p>
                                    <img src={movie.img_url} alt={movie.movie_name} />
                                    <p>
                                        <strong>Meta Score:</strong> <span className={metaColor}>{movie.meta_score}</span>
                                    </p>
                                    <p>
                                        <strong>Runtime:</strong> {movie.movie_runtime}
                                    </p>
                                    <p>
                                        <strong>Rating:</strong> {movie.rating}
                                    </p>
                                    <p>
                                        <strong>Release Year:</strong> {movie.release_year}
                                    </p>
                                    <p>{movie.summary}</p>
                                </div>
                            );
                        })}
                    </section>
                )}
            </main>
        </Fragment>
    );
};
export default App;

При необходимости перезагрузите сервер Node и убедитесь, что он также работает. Вы должны увидеть, как приложение работает в вашем браузере. Он также имеет форму, которая позволяет вам добавлять новые записи в базу данных, которые автоматически отображаются на странице. Мета-баллы имеют даже цветовую кодировку в зависимости от их количества, что делается с помощью оператора if, который вы можете видеть в коде.

Приложение подключено к вашей локальной базе данных PostgreSQL, однако достаточно легко изменить конечную точку для API на HarperDB. Все остальные маршруты находятся в бэкенде, так что вы можете поиграть с ними и подключить их к переднему краю, что, я уверен, вы уже в состоянии сделать.

Когда вы добавляете новый фильм, он не перенаправляет обратно на главную страницу реакции. Если вы хотите добавить эту функциональность, обновите функцию post route в файле backend index.js в разделе PostgreSQL с помощью приведенного ниже кода. Перезагрузите внутренний сервер, чтобы увидеть изменения.

// POST: Create movies and add them to the database
app.post('/add-movie', (req, res) => {
    const { movieName, imgUrl, releaseYear, summary, director, genre, rating, movieRuntime, metaScore } = req.body;
    db('movies')
        .insert({
            movie_name: movieName,
            img_url: imgUrl,
            release_year: releaseYear,
            summary: summary,
            director: director,
            genre: genre,
            rating: rating,
            movie_runtime: movieRuntime,
            meta_score: metaScore,
        })
        .then(() => {
            console.log('Movie Added');
            // return res.json({ msg: 'Movie Added' });
            return res.redirect('http://localhost:3000');
        })
        .catch((err) => {
            console.log(err);
        });
});