创建连接到 PostgreSQL 和 HarperDB 的 React/Node 应用
我相信你们大多数人已经非常熟悉 MERN 技术栈了。它拥有一个 React 前端和一个 Node/Express 后端,后端连接到 MongoDB 数据库。接下来,我将向你们展示如何轻松地连接到一个使用 PostgreSQL 数据库来持久化数据的 Node 后端。此外,我还会额外展示如何连接到https://harperdb.io/,这是一个 SQL/NoSQL 数据管理平台。它拥有完整的索引,不会重复数据,并且可以在从边缘到云端的任何设备上运行。
我假设您已经了解 JavaScript、Node 和 SQL,因为本指南只是一个快速介绍。
您将创建一个如下图所示的应用程序。
先决条件
- 安装了 Insomnia 或 Postman API 应用
- 您的计算机上安装了 NPM/Node
- PostgreSQL 安装和设置
创建 PostgreSQL 数据库
对于本指南,我将使用 Valentina Studio 作为 GUI 来管理本地 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 向表 movies 中添加一些数据。
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 来查看表 movies 中的所有数据。
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
在代码编辑器中打开项目,然后在index.js
文件中创建一个 Node 服务器
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}`));
重启服务器,然后打开浏览器窗口并重新加载页面。你应该会看到数据库中 movies 表的数据以 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 格式返回的与该 ID 匹配的电影。只要 ID 号在数据库中,它就可以使用。
POST:创建电影并将其添加到数据库
向http://127.0.0.1:5000/add-movie发送一个 POST 请求,并传入示例截图中所示的键值对数据。然后前往“获取所有影片”路由查看新条目。您也可以直接使用数据库 GUI 或 CLI 查看新的数据库条目。
DELETE:从数据库中根据 movieId 删除电影
向路由http://127.0.0.1:5000/delete-movie发送 DELETE 请求,使用名称 movieId。值请使用数据库中的任意 ID 来删除该条目。
PUT:根据 movieId 从数据库中更新电影
使用您的 API 工具并向http://127.0.0.1:5000/update-movie发送 PUT 请求,以更新数据库中的条目。请前往文件底部index.js
查看 UPDATE 路由的代码。您可以更改 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
干得好,你刚刚创建了一个连接到 PostgreSQL 数据库的 Node 应用。下一节将介绍 HarperDB。
创建HarperDB数据库
首先,您需要创建一个 HarperDB 帐户,然后创建一个数据库。我将数据库命名为“movies”。创建和设置 HarperDB 数据库非常简单。只需按照此视频HarperDB Cloud Launch Tour进行操作即可,您也可以在此处查看 HarperDB 与 Node 的文档https://docs.harperdb.io/。
登录凭证
您需要授权码才能连接到 HarperDB。首先,使用您的 API 工具向 HarperDB 的 URL 发送一个 GET 请求,其中包含您的用户名和密码。您需要使用 Basic Auth。然后,点击“生成授权码”按钮,选择 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 工具或查看浏览器中的路由,查看 HarperDB 数据库实例返回的 JSON 数据。对于更新路由,只需使用 API 工具并输入如下所示的键值对即可。
HarperDB 将 ID 存储为字符串,因此请注意,如果电影 ID 是数字,则无法通过 movieId 获取、更新和删除电影,除非您对代码进行一些调整。我们一直以来都将 ID 存储为数字,但只需将数字替换movieId
为字符串即可轻松切换。
您需要重新启动节点服务器才能看到更改。
构建前端
现在是时候创建一个从 API 获取数据的前端了。cd
进入 meta-movies-app 的根文件夹,然后运行下面的命令在 React 中设置一个项目。
npx create-react-app frontend
cd frontend
npm start
现在使用或启动 React 应用服务器yarn start
进入你的 React 项目,删除文件中的所有 CSS index.css
。接下来,用下面的代码替换App.css
和App.js
文件中的代码。
应用程序.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 服务器,并确保它正在运行。您应该会在浏览器中看到该应用运行。它还有一个表单,允许您添加新的数据库条目,这些条目会自动显示在页面上。Meta 评分甚至会根据其数值进行颜色编码,这是通过 if 语句实现的,您可以在代码中看到。
该应用程序连接到您本地的 PostgreSQL 数据库,但将 API 的端点更改为 HarperDB 也很容易。所有其他路由都在后端,因此您可以自行尝试并将它们连接到前端,我相信您已经能够做到这一点。
添加新影片时,它不会重定向回 React 主页。如果您想添加此功能,请index.js
使用以下代码更新 PostgreSQL 部分后端文件中的 post 路由函数。重启后端服务器即可查看更改。
// 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);
});
});