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

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

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

Предпосылки

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

https://pypi.org/project/Flask/

https://pypi.org/project/Flask-Cors/

https://pypi.org/project/python-dotenv/

https://pypi.org/project/psycopg2/ (включая бинарники внизу) pip install psycopg2-binary

https://pypi.org/project/harperdb/

Создайте базу данных 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

Создайте внутренний сервер Flask

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

Если у вас возникли проблемы с запуском виртуальной среды, прочитайте эту документацию https://docs.python.org/3/library/venv.html.

mkdir meta-movies-app
cd meta-movies-app
python3 -m venv backend
. backend/bin/activate
cd backend
touch index.py

Откройте проект в редакторе кода, а затем создайте сервер Python/Flask в файле index.py.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return 'Home Page Route'

Настройте среду разработки, запустив эти команды в своем терминале.

export FLASK_APP=index.py   
export FLASK_ENV=development

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

flask run

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

Сначала создайте файл .gitignore и поместите его в корневую папку для бэкенда с приведенным ниже кодом внутри него.

.env

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

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

Теперь обновите файл index.py в корневой папке с помощью приведенного ниже кода.

from flask import Flask, jsonify
from flask_cors import CORS
from dotenv import load_dotenv
import psycopg2
import os
load_dotenv()
# PostgreSQL Database credentials loaded from the .env file
DATABASE = os.getenv('DATABASE')
DATABASE_USERNAME = os.getenv('DATABASE_USERNAME')
DATABASE_PASSWORD = os.getenv('DATABASE_PASSWORD')
app = Flask(__name__)
# CORS implemented so that we don't get errors when trying to access the server from a different server location
CORS(app)

try:
    con = psycopg2.connect(
        database=DATABASE,
        user=DATABASE_USERNAME,
        password=DATABASE_PASSWORD)
    cur = con.cursor()
    # GET: Fetch all movies from the database
    @app.route('/')
    def fetch_all_movies():
        cur.execute('SELECT * FROM movies')
        rows = cur.fetchall()
        print(rows)
        return jsonify(rows)
except:
    print('Error')

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

Вы можете посмотреть документацию по пакету psycopg2, чтобы узнать больше о коде https://www.psycopg.org/docs/

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

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

from flask import Flask, jsonify, request
from flask_cors import CORS
from dotenv import load_dotenv
import psycopg2
import os
load_dotenv()
# PostgreSQL Database credentials loaded from the .env file
DATABASE = os.getenv('DATABASE')
DATABASE_USERNAME = os.getenv('DATABASE_USERNAME')
DATABASE_PASSWORD = os.getenv('DATABASE_PASSWORD')
app = Flask(__name__)
# CORS implemented so that we don't get errors when trying to access the server from a different server location
CORS(app)

try:
    con = psycopg2.connect(
        database=DATABASE,
        user=DATABASE_USERNAME,
        password=DATABASE_PASSWORD)
    cur = con.cursor()
    # GET: Fetch all movies from the database
    @app.route('/')
    def fetch_all_movies():
        cur.execute('SELECT * FROM movies')
        rows = cur.fetchall()
        print(rows)
        return jsonify(rows)
    # GET: Fetch movie by movieId from the database
    @app.route('/<int:movie_id>')
    def fetch_by_id(movie_id=None):
        cur.execute(f'SELECT * FROM movies WHERE movie_id = {movie_id}')
        rows = cur.fetchall()
        print(rows)
        return jsonify(rows)
    # POST: Create movies and add them to the database
    @app.route('/add-movie', methods=['GET', 'POST'])
    def add_movie():
        if request.method == 'POST':
            data = request.form.to_dict()
            print(data)
            cur.execute("INSERT INTO movies (movie_name, img_url, release_year, summary, director, genre, rating, movie_runtime, meta_score) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
                        (f"{data['movieName']}", f"{data['imgUrl']}", data['releaseYear'], f"{data['summary']}",
                         f"{data['director']}", f"{data['genre']}", f"{data['rating']}", data['movieRuntime'], data['metaScore']))
            con.commit()
            return 'Form submitted'
        else:
            return 'Form submission failed'
    # DELETE: Delete movie by movieId from the database
    @app.route('/delete-movie', methods=['GET', 'DELETE'])
    def delete_by_id():
        movie_id = request.form.to_dict()
        print(movie_id['movieId'])
        cur.execute(
            f"DELETE FROM movies WHERE movie_id = {movie_id['movieId']} RETURNING movie_name")
        con.commit()
        return 'Movie Deleted'
    # PUT: Update movie by movieId from the database
    @app.route('/update-movie', methods=['GET', 'PUT'])
    def update_by_id():
        cur.execute(
            'UPDATE movies SET movie_name = \'Goldeneye\' WHERE movie_id = 1')
        con.commit()
        return 'Movie Updated'
except:
    print('Error')

Использование инструмента 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 с данными пары ключ-значение, как показано на скриншоте примера. Затем перейдите к маршруту Выбрать все фильмы, чтобы увидеть новую запись. В качестве альтернативы вы можете просто использовать графический интерфейс вашей базы данных или CLI, чтобы увидеть новую запись в базе данных.

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

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

PUT: обновить фильм по movieId из базы данных

Просто перейдите на http://127.0.0.1:5000/update-movie с помощью приложения API или в браузере, чтобы обновить запись в базе данных. Перейдите к нижней части файла index.py, чтобы увидеть код маршрута UPDATE. Вы можете изменить запрос SQL, чтобы обновить любое из полей в таблице, а затем все, что вам нужно сделать, это выбрать movie_id, чтобы обновить его запись. Вы можете увидеть код Python и SQL-запрос ниже.

питон

# PUT: Update movie by movieId from the database
    @app.route('/update-movie', methods=['GET', 'PUT'])
    def update_by_id():
        cur.execute(
            'UPDATE movies SET movie_name = \'Goldeneye\' WHERE movie_id = 1')
        con.commit()
        return 'Movie Updated'

SQL

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

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

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

Сначала вам нужно создать учетную запись HarperDB, а затем создать базу данных. Я назвал свою базу данных фильмы. Создать и настроить базу данных HarperDB очень просто. Просто просмотрите это видео HarperDB Cloud Launch Tour, и вы также можете ознакомиться с документацией для пакета HarperDB Python здесь https://pypi.org/project/harperdb/.

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

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

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

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

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

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

from flask import Flask, jsonify, request
from flask_cors import CORS
from dotenv import load_dotenv
import psycopg2
import os
import harperdb
load_dotenv()
# PostgreSQL Database credentials loaded from the .env file
DATABASE = os.getenv('DATABASE')
DATABASE_USERNAME = os.getenv('DATABASE_USERNAME')
DATABASE_PASSWORD = os.getenv('DATABASE_PASSWORD')
# HarperDB Database credentials loaded from the .env file
HARPERDB_URL = os.getenv('HARPERDB_URL')
HARPERDB_USERNAME = os.getenv('HARPERDB_USERNAME')
HARPERDB_PASSWORD = os.getenv('HARPERDB_PASSWORD')
db = harperdb.HarperDB(
    url=HARPERDB_URL,
    username=HARPERDB_USERNAME,
    password=HARPERDB_PASSWORD)
app = Flask(__name__)
# CORS implemented so that we don't get errors when trying to access the server from a different server location
CORS(app)

try:
    con = psycopg2.connect(
        database=DATABASE,
        user=DATABASE_USERNAME,
        password=DATABASE_PASSWORD)
    cur = con.cursor()
    # GET: Fetch all movies from the database
    @app.route('/')
    def fetch_all_movies():
        cur.execute('SELECT * FROM movies')
        rows = cur.fetchall()
        print(rows)
        return jsonify(rows)
    # GET: Fetch movie by movieId from the database
    @app.route('/<int:movie_id>')
    def fetch_by_id(movie_id=None):
        cur.execute(f'SELECT * FROM movies WHERE movie_id = {movie_id}')
        rows = cur.fetchall()
        print(rows)
        return jsonify(rows)
    # POST: Create movies and add them to the database
    @app.route('/add-movie', methods=['GET', 'POST'])
    def add_movie():
        if request.method == 'POST':
            data = request.form.to_dict()
            print(data)
            cur.execute("INSERT INTO movies (movie_name, img_url, release_year, summary, director, genre, rating, movie_runtime, meta_score) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
                        (f"{data['movieName']}", f"{data['imgUrl']}", data['releaseYear'], f"{data['summary']}",
                         f"{data['director']}", f"{data['genre']}", f"{data['rating']}", data['movieRuntime'], data['metaScore']))
            con.commit()
            return 'Form submitted'
        else:
            return 'Form submission failed'
    # DELETE: Delete movie by movieId from the database
    @app.route('/delete-movie', methods=['GET', 'DELETE'])
    def delete_by_id():
        movie_id = request.form.to_dict()
        print(movie_id['movieId'])
        cur.execute(
            f"DELETE FROM movies WHERE movie_id = {movie_id['movieId']} RETURNING movie_name")
        con.commit()
        return 'Movie Deleted'
    # PUT: Update movie by movieId from the database
    @app.route('/update-movie', methods=['GET', 'PUT'])
    def update_by_id():
        cur.execute(
            'UPDATE movies SET movie_name = \'Goldeneye\' WHERE movie_id = 1')
        con.commit()
        return 'Movie Updated'
    # HarperDB Database routes
    # GET: Fetch all movies from the database
    @app.route('/harperdb')
    def harperdb_fetch_all():
        fetch_all = db._sql('SELECT * FROM dev.movies')
        print(fetch_all)
        return jsonify(fetch_all)
    # GET: Fetch movie by movieId from the database
    @app.route('/harperdb/<string:movie_id>')
    def harperdb_fetch_by_id(movie_id=None):
        # For fetching ID's that are numbers
        fetch_by_id = db._sql(
            f"SELECT * FROM dev.movies WHERE id = {movie_id}")
        # For fetching ID's that are strings
        # fetch_by_id = db._sql(
        #     f"SELECT * FROM dev.movies WHERE id = '{movie_id}'")
        print(fetch_by_id)
        return jsonify(fetch_by_id)
    # POST: Create movies and add them to the database
    @app.route('/harperdb/add-movie', methods=['GET', 'POST'])
    def harperdb_add_movie():
        if request.method == 'POST':
            data = request.form.to_dict()
            print(data)
            (f"{data['movieName']}", f"{data['imgUrl']}", data['releaseYear'], f"{data['summary']}",
             f"{data['director']}", f"{data['genre']}", f"{data['rating']}", data['movieRuntime'], data['metaScore'])
            add_new_movie = db._sql(
                f"INSERT INTO dev.movies(movie_name, img_url, release_year, summary, director, genre, rating, movie_runtime, meta_score) VALUES('{data['movieName']}', '{data['imgUrl']}', {data['releaseYear']}, '{data['summary']}', '{data['director']}', '{data['genre']}', '{data['rating']}', {data['movieRuntime']}, {data['metaScore']})")
            print(add_new_movie)
            return 'Form submitted'
        else:
            return 'Form submission failed'
    # DELETE: Delete movie by movieId from the database
    @app.route('/harperdb/delete-movie', methods=['GET', 'DELETE'])
    def harperdb_delete_by_id():
        movie_id = request.form.to_dict()
        print(movie_id['movieId'])
        # For fetching ID's that are numbers
        db._sql(
            f"DELETE FROM dev.movies WHERE id = {movie_id['movieId']}")
        # For fetching ID's that are strings
        # db._sql(
        #     f"DELETE FROM dev.movies WHERE id = '{movie_id['movieId']}'")
        return 'Movie Deleted'
    # PUT: Update movie by movieId from the database
    @app.route('/harperdb/update-movie', methods=['GET', 'PUT'])
    def harperdb_update_by_id():
        # For fetching ID's that are numbers
        db._sql(
            'UPDATE dev.movies SET movie_name = \'Goldeneye\' WHERE id = 7')
        # For fetching ID's that are strings
        # db._sql(
        #     f"UPDATE dev.movies SET movie_name = \'Goldeneye\' WHERE id = '42e7603f-f7ee-413d-9a0b-384ef04ca7de'")
        return 'Movie Updated'
except:
    print('Error')

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

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

Построение передней части

Пришло время создать интерфейс, который будет получать данные из API. cd в корневую папку мета-фильмов-приложения, а затем выполните приведенную ниже команду, чтобы настроить проект в 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) => {
                            const movieId = movie[0];
                            const movieName = movie[1];
                            const movieImgUrl = movie[2];
                            const movieReleaseYear = movie[3];
                            const movieSummary = movie[4];
                            const movieDirector = movie[5];
                            const movieGenre = movie[6];
                            const movieRating = movie[7];
                            const movieRuntime = movie[8];
                            const movieMetaScore = movie[9];
                            let metaColor = 'low';
                            if (movieMetaScore >= 70) {
                                metaColor = 'high';
                            } else if (movieMetaScore <= 69 && movieMetaScore >= 49) {
                                metaColor = 'medium';
                            } else {
                                metaColor = 'low';
                            }
                            return (
                                <div className="movie-container" key={String(movieId)}>
                                    <h1>{movieName}</h1>
                                    <p>
                                        <strong>Director:</strong> {movieDirector}
                                    </p>
                                    <p>
                                        <strong>Genre:</strong> {movieGenre}
                                    </p>
                                    <img src={movieImgUrl} alt={movieName} />
                                    <p>
                                        <strong>Meta Score:</strong> <span className={metaColor}>{movieMetaScore}</span>
                                    </p>
                                    <p>
                                        <strong>Runtime:</strong> {movieRuntime}
                                    </p>
                                    <p>
                                        <strong>Rating:</strong> {movieRating}
                                    </p>
                                    <p>
                                        <strong>Release Year:</strong> {movieReleaseYear}
                                    </p>
                                    <p>{movieSummary}</p>
                                </div>
                            );
                        })}
                    </section>
                )}
            </main>
        </Fragment>
    );
};
export default App;

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

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

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

from flask import Flask, jsonify, request, redirect
# POST: Create movies and add them to the database
    @app.route('/add-movie', methods=['GET', 'POST'])
    def add_movie():
        if request.method == 'POST':
            data = request.form.to_dict()
            print(data)
            cur.execute("INSERT INTO movies (movie_name, img_url, release_year, summary, director, genre, rating, movie_runtime, meta_score) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
                        (f"{data['movieName']}", f"{data['imgUrl']}", data['releaseYear'], f"{data['summary']}",
                         f"{data['director']}", f"{data['genre']}", f"{data['rating']}", data['movieRuntime'], data['metaScore']))
            con.commit()
            # return 'Form submitted'
            return redirect('http://localhost:3000', code="200")
        else:
            return 'Form submission failed'