如何使用 React、Spotify 和 Fauna 构建音乐播放列表
什么是 Spotify?
Spotify Web API 入门
什么是动物区系?
Fauna DB 入门
构建应用程序
结论
与“与动物一起写作”计划相关而撰写。
使用 React、Spotify API 和 Fauna 数据库,我们可以构建个性化的音乐播放列表。
在本文中,我将逐步展示普通开发人员构建此应用程序的步骤。我们将学习如何使用 Spotify Web API 验证用户身份并搜索音乐,同时使用 Fauna 进行数据管理。
什么是 Spotify?
Spotify是一家音乐流媒体服务提供商。它提供了一个开发者工具(Spotify Web API),允许开发者访问用户和音乐相关数据。在本文中,我们将使用 Spotify 进行用户身份验证并将其用作音乐目录。
Spotify Web API 入门
要在应用程序中使用 Spotify Web API:
- 通过在www.spotify.com注册来创建 Spotify 帐户。
- 登录并转到开发者仪表板https://developer.spotify.com/dashboard。
- 按照以下步骤注册您的应用程序:https://developer.spotify.com/documentation/general/guides/authorization/app-settings/。
- 记下/保存
CLIENT ID
为应用程序生成的 Spotify。 - 确保将应用程序的重定向 URI 设置为
http://localhost:3000/
。最好在将应用程序托管在公共域时更改此设置。
什么是动物区系?
Fauna 是一个云 API,提供灵活、无服务器且友好的数据库实例。在本文中,我们将使用 Fauna 来存储应用程序中使用的用户和音乐相关数据。
Fauna DB 入门
要使用 Fauna DB:
- 通过以下网址注册创建帐户:https://dashboard.fauna.com/accounts/register
为我们的应用程序创建数据库
- 注册后,登录仪表板并单击
CREATE DASHBOARD
。 - 在出现的表单中,输入数据库名称并选择
Classic
区域。 - 单击
CREATE
按钮。
创建集合
集合是存储在 JSON 对象中的一组相关数据。
在本应用中,我们需要两个集合:users
和playlists
。
创建这些集合的步骤如下:
- 点击
NEW COLLECTION
。 - 输入收藏集名称。
- 单击
SAVE
按钮。
对用户和播放列表集合重复上述步骤。
创建索引
索引是除默认引用之外的文档引用,用于增强文档的检索或查找。
对于此应用程序,我们需要两个索引:
playlist_for_user
检索特定用户创建的所有播放列表。user_by_user_id
检索包含特定用户数据的文档。
要创建这些索引:
- 点击
NEW INDEX
。 -
对于
playlist_for_user
索引,请在适用的情况下输入以下详细信息:- 源集合 - 播放列表
- 索引名称 - playlist_for_user
- 条款 - data.user_id
- 独特的 -
unchecked
-
对于
user_by_user_id
索引,请在适用的情况下输入以下详细信息:- 源集合-用户
- 索引名称 - user_by_user_id
- 条款 - data.user_id
- 独特的 -
checked
生成你的 Fauna 密钥
这个密钥将我们的应用程序连接到数据库。
要生成密钥,请执行以下操作:
- 在左侧导航菜单上,单击“安全”。
- 点击
NEW KEY
。 - 输入您的密钥名称。
- 单击
SAVE
后将为您生成一个新密钥。
确保将秘密保存在安全的地方。
构建应用程序
设置应用程序
首先,我创建了一个启动应用程序来引导我们的构建过程。
你需要从这个GitHub 仓库克隆它,并在终端中运行以下命令:
git clone https://github.com/wolz-CODElife/Spotify-Playlist-Manager-With-FaunaDB.git
在下载的文件夹中,存在以下目录和文件:
我们将要使用的文件夹和文件是里面的src
,它们是:
app.js
:此文件将包含视图(路线)。/utils/models.js
:这个文件是我们与 Fauna 数据库进行通信的地方。/utils/Spotify.js
:这个文件是我们与 Spotify Web API 通信的地方。- 里面的文件
components
是我们用来构建应用程序用户界面的反应组件。
安装项目的依赖项
该应用程序使用了几个 Node 包,您需要安装这些包才能正常运行。要安装这些包,请在终端中运行以下代码:
cd Spotify-Playlist-Manager-With-FaunaDB
npm install
安装 FaunaDB 节点包
为了让我们的应用程序能够与之前创建的数据库进行通信,我们需要安装 Fabia 提供的 Node 包。为此,请打开终端并输入:
npm install faunadb
启动应用程序
要运行我们的应用程序,请打开终端并输入:
npm start
这应该编译 React 应用程序并将其托管在 上http://localhost:3000/
,终端应该显示以下结果:
现在,打开您的浏览器并搜索http://localhost:3000
,这将在您的浏览器上显示下面的图像。
创建我们的路线
我们的应用程序将有四条路线:Index
、、和。Create
MyCollections
Error
- 索引:用户在身份验证之前启动应用程序时首先看到的主页。
- 创建:用户认证后搜索音乐并创建自己喜欢的音乐播放列表的页面。
- MyCollections:用户浏览和管理其保存的播放列表的页面。
- 错误:当用户进入未定义的路线时出现的页面。
我们将通过将以下代码放入 中来定义我们的路线App.js
。
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Index from './components/Index';
import Create from './components/Create';
import Error from './components/Error'
import MyCollections from './components/MyCollections';
const App = () => {
return (
<Router>
<Switch>
<Route exact path="/" children={<Index />} />
<Route path="/create" children={<Create />} />
<Route path="/mycollections" children={<MyCollections /> } />
<Route path="*" children={Error} />
</Switch>
</Router>
)
}
export default App;
我们在这里所做的是检查特定path
用户,然后将相关组件呈现为children
道具Route
。
获取 Spotify 数据
我们需要三个函数:getAccessToken
、getUserId
和search
。
getAccessToken
:此函数向 Spotify 授权 API 发送请求,如果用户接受或授权 Spotify 与我们的应用程序共享他/她的数据,Spotify 将返回一个,accesstoken
我们的应用程序稍后可以使用它来安全地向其他 Spotify API 路由发出请求。getUserId
:此函数向 Spotify 发送请求,如果accessToken
通过身份验证,Spotify 会将用户数据返回给我们的应用程序。search
:此函数发送带有参数的请求term
,Spotify 将返回符合term
用户搜索的音乐曲目。
const clientId = "YOUR-SPOTIFY-CLIENT-ID"
const redirectUri = encodeURIComponent("http://localhost:3000/")
const scopes = encodeURIComponent("user-read-private user-read-email playlist-modify-public")
let accessToken
const Spotify = {
getAccessToken : () => {
if(localStorage.getItem('accessToken')){
return JSON.parse(localStorage.getItem('accessToken'))
}
accessToken = window.location.hash
.substring(1)
.split('&')
.reduce((initial, item) => {
let parts = item.split('=')
initial[parts[0]] = decodeURIComponent(parts[1])
return initial
}, {}).access_token
if (accessToken) {
localStorage.setItem('accessToken', JSON.stringify(accessToken))
return accessToken
}
else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes}&response_type=token`
window.location = accessUrl
}
},
getUserId: () => {
accessToken = Spotify.getAccessToken()
if (accessToken) {
const headers = { Authorization: `Bearer ${accessToken}` }
return fetch("https://api.spotify.com/v1/me", { headers: headers })
.then(response => response.json())
.then(jsonResponse => {
if (jsonResponse) {
const { id, display_name, email, external_urls, images } = jsonResponse
const profile = {
user_id: id,
email: email,
name: display_name,
image: images[0].url,
url: external_urls.spotify
}
return profile
}
})
}
},
search : (term) => {
accessToken = Spotify.getAccessToken()
if (accessToken) {
const headers = {Authorization: `Bearer ${accessToken}`}
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {headers: headers})
.then(response => { return response.json() })
.then(jsonResponse => {
if (!jsonResponse.tracks) {
return []
}
return jsonResponse.tracks.items.map(track => ({
id: track.id,
name: track.name,
artist: track.artists[0].name,
album: track.album.name,
image: track.album.images[1].url,
uri: track.uri
}))
})
}
}
}
export default Spotify
创建模型
我们的应用程序有六个模型函数:createUser
、、、和getUser
。savePlaylist
getPlaylists
getPlaylist
deletePlaylist
createUser
:获取用户数据并在我们的 Fauna 数据库中创建一个新文档。如果已经有用户使用相同的 Spotify ID 注册,应用程序将抛出错误,因为我们将索引设置user_by_user_id
为仅接受唯一值user_id
,否则,将用户数据存储在数据库中,并创建一个本地存储项,该项将保存用户的详细信息,直到用户注销。getUser
:接受一个参数user_id
并使用索引查询数据库user_by_user_id
。savePlaylist
:获取user_id
,以及用户已选择的音乐列表。getPlaylists
;获取user_id
并返回该用户创建的所有播放列表集合。getPlaylist
:获取id
播放列表并返回该播放列表中的音乐列表。deletePlaylist
:获取id
播放列表并删除该收藏集。
要创建我们的模型,/utils/models.js
将包含以下代码:
import faunadb, { query as q } from 'faunadb'
const client = new faunadb.Client({ secret: "YOUR-FAUNA-SECRET-KEY" })
export const createUser = async ({user_id, email, name, image, url}) => {
try {
const user = await client.query(
q.Create(
q.Collection('users'),
{
data: {user_id, email, name, image, url}
}
)
)
localStorage.setItem('user', JSON.stringify(user.data))
return user.data
} catch (error) {
return
}
}
export const getUser = async (user_id) => {
try {
const user = await client.query(
q.Get(
q.Match(q.Index('user_by_user_id'), user_id)
)
)
localStorage.setItem('user', JSON.stringify(user.data))
return user.data
}
catch (error) {
return
}
}
export const savePlaylist = async (user_id, name, tracks) => {
if(!name || !tracks.length){
return
}
try {
const playlists = await client.query(
q.Create(
q.Collection('playlists'),
{
data: {user_id, name, tracks}
}
)
)
return playlists.data
} catch (error) {
return
}
}
export const getPlaylists = async (user_id) => {
let playlistList = []
try {
const playlists = await client.query(
q.Paginate(
q.Match(q.Index('playlist_for_user'), user_id)
)
)
for (let playlistID of playlists.data) {
let playlist = await getPlaylist(playlistID.value.id)
playlistList.push(playlist)
}
return playlistList
} catch (error) {
return
}
}
export const getPlaylist = async (id) => {
try {
const playlist = await client.query(
q.Get(q.Ref(q.Collection('playlists'), id))
)
playlist.data.id = playlist.ref.value.id
return playlist.data
} catch (error) {
return
}
}
export const deletePlaylist = async (id) => {
try {
const playlist = await client.query(
q.Delete(
q.Ref(q.Collection('playlists'), id)
)
)
playlist.data.id = playlist.ref.value.id
return playlist.data
} catch (error) {
return
}
}
创建索引页
当应用程序首次运行或用户转到/
路由时,我们希望应用程序呈现身份验证页面。
当 Index 组件加载时:如果用户已登录,我们使用useHistory
钩子将用户重定向到“/create”,否则,我们希望显示 Index 组件的内容。
登录和注册按钮有一个 onClick 事件监听器,当点击时会调用相应的函数。
该Signup
函数从函数中获取用户的 Spotify ID Spotify.getUserId
,然后尝试使用获取的 Spotify ID 在我们的 Fauna 数据库中创建一个新用户。如果该 ID 在显示错误消息之前已被注册,我们将用户重定向到“/create”路由。
该Login
函数还会从函数中获取用户的 Spotify ID Spotify.getUserId
,然后查询 Fauna 数据库,查找具有该 ID 的用户。如果未找到该 ID 作为用户,则显示错误消息,否则重定向到“/create”路由。
/components/Index.js
将包含以下代码:
import React from 'react'
import { useHistory } from 'react-router-dom'
import { createUser, getUser } from '../utils/model'
import Spotify from '../utils/Spotify'
const Index = () => {
const history = useHistory()
if (localStorage.getItem('user')) {
history.push('/create')
}
const Signup = () => {
Spotify.getUserId().then((newUserData) => {
createUser(newUserData)
.then(req => {
if (req)
history.push('/create')
else
alert("Spotify account already registered!")
})
.catch((err) => console.log(err.message))
})
}
const Login = () => {
Spotify.getUserId().then((newUserData) => {
getUser(newUserData.user_id)
.then(req => {
if (req)
history.push('/create')
else
alert('Spotify account not found! Signup first')
})
.catch((err) => console.log(err.message))
})
}
return (
<>
<div className="container">
<br /><br /><br />
<h1>MusicBuddy</h1>
<br /><br />
<span className="btn" onClick={() => Login()}>Login</span>
<br /><br />
<p>OR</p>
<span className="btn" onClick={() => Signup()}>SignUp</span>
</div>
</>
)
}
export default Index
创建 NavBar 组件
NavBar 组件是我们存放用户资料、导航链接和注销按钮的地方。
NavBar 接受一个名为 的 props userData
。我们还设置了一个 state,用于检查用户的个人资料下拉菜单是否可见。带有 atrribute 的 divclassName="dropDown"
具有 onMouseEnter 和 onMouseLeave 事件,用于将userProfile
状态更改为 true 或 false。当userProfile
状态为 true 时,<ul>
包含用户个人资料的标签将被渲染,否则将被隐藏。
注销按钮有一个 onClick 事件监听器,它会清除本地存储。
components/NavBar.js
将包含以下代码:
import React, { useState} from 'react'
import { Link } from 'react-router-dom'
import userImg from '../assets/justin.PNG'
const NavBar = ({ userData }) => {
const [userProfile, setUserProfile] = useState(false)
return (
<>
<div >
<div className="dropDown" onMouseEnter={() => setUserProfile(!userProfile)} onMouseLeave={() => setUserProfile(false)}>
<img src={userData?.image || userImg} alt="user" />
{userProfile && <ul>
<li><h3>{ userData?.name || 'John Doe' }</h3></li>
<li>
<p >
<a href={userData?.url || '/'} target="_blank" rel="noopener noreferrer">{'Profile >>'}</a>
</p>
</li>
</ul>}
</div>
<div>
<Link to="/" className="btn">Home</Link>
<Link to="/mycollections" className="btn">Collections</Link>
<Link to="/" className="btn" onClick={() => localStorage.clear()}>Logout</Link>
</div>
</div>
</>
)
}
export default NavBar
创建新播放列表页面
该组件包含另外三个组件:NavBar
、PlayList
和SearchResults
。
SearchResults
允许用户在我们的应用程序中搜索音乐并从 Spotify API 获取结果。PlayList
允许用户创建一些选定音乐的播放列表并将其存储在数据库中。
/components/create.js
将包含以下代码:
import React, { useState, useEffect } from 'react'
import PlayList from './PlayList'
import SearchResults from './SearchResults'
import Spotify from '../utils/Spotify'
import NavBar from './NavBar'
import { useHistory } from 'react-router-dom'
import { savePlaylist } from '../utils/model'
const Create = () => {
const history = useHistory()
const [userData, setUserData] = useState(JSON.parse(localStorage.getItem("user")))
useEffect(() => {
if (!localStorage.getItem('user')) {
history.push('/')
}
setUserData(JSON.parse(localStorage.getItem("user")))
}, [history])
const [searchResults, setSearchResults] = useState([])
const [playListName, setPlayListName] = useState("")
const [playListTracks, setPlayListTracks] = useState([])
const search = (term) => {
if (term !== "") {
Spotify.search(term).then((searchResults) => setSearchResults(searchResults))
}
else {
document.querySelector("#searchBar").focus()
}
}
const addTrack = (track) => {
if (playListTracks.find((savedTrack) => savedTrack.id === track.id)) {
return
}
const newPlayListTracks = [...playListTracks, track]
setPlayListTracks(newPlayListTracks)
}
const removeTrack = (track) => {
const newPlayListTracks = playListTracks.filter((currentTrack) => currentTrack.id !== track.id)
searchResults.unshift(track)
setPlayListTracks(newPlayListTracks)
}
const removeTrackSearch = (track) => {
const newSearchResults = searchResults.filter((currentTrack) => currentTrack.id !== track.id)
setSearchResults(newSearchResults)
}
const doThese = (track) => {
addTrack(track)
removeTrackSearch(track)
}
const updatePlayListname = (name) => {
setPlayListName(name)
}
const savePlayList = (e) => {
e.preventDefault()
if (playListName !== "") {
alert('Playlist added successfully...')
savePlaylist(userData.user_id, playListName, playListTracks)
.then(req => {
if (req) {
setPlayListName("")
setPlayListTracks([])
}
})
}
else {
document.querySelector('#playListName').focus()
}
}
return (
<>
<NavBar userData={userData}/>
<div className="container">
<h1 >MusicBuddy</h1>
<article className="section">
<SearchResults search={search} searchResults={searchResults} onAdd={doThese} />
<PlayList playListTracks={playListTracks} playListName={playListName} onNameChange={updatePlayListname} onRemove={removeTrack} onSave={savePlayList} />
</article>
</div>
</>
)
}
export default Create
创建搜索结果组件
该组件包含SearchBar
和TrackList
组件。
SearchBar
组件包含一个表单,供用户从 Spotify 搜索随机歌曲。TrackList
组件显示搜索结果。
/components/SearchResults.js
应该包含以下代码:
import React, { useState } from 'react'
import TrackList from './TrackList'
const SearchResults = ({ search, searchResults, onAdd }) => {
return (
<>
<div className="trackList">
<SearchBar onSearch={search} />
<TrackList tracks={searchResults} onAdd={onAdd} />
</div>
</>
)
}
const SearchBar = ({ onSearch }) => {
const [term, setTerm] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
onSearch(term);
};
return (
<>
<form className="form" onSubmit={handleSubmit}>
<input
id="searchBar"
type="text"
placeholder="Song, album or artist name"
onChange={(e) => setTerm(e.target.value)}
/>
<button className="btn" onClick={handleSubmit}>
SEARCH
</button>
</form>
</>
);
};
export default SearchResults
创建播放列表组件
该组件包含一个表单和TrackList
组件。
- 该表单用于设置用户正在创建的播放列表的名称。
TrackList
显示用户将创建的播放列表中包含的音乐列表。
/components/PlayList.js
将包含以下代码:
import React from "react";
import TrackList from "./TrackList";
const PlayList = ({ onNameChange, playListTracks, playListName, onRemove, onSave }) => {
return (
<>
<div className="trackList">
<form className="form" onSubmit={onSave}>
<input id="playListName" type="text" onChange={(e) => onNameChange(e.target.value)} defaultValue={playListName} placeholder="Playlist Name" />
{(playListTracks.length > 0) &&
<button className="btn" onClick={onSave}>
Save to Collections
</button>}
</form>
<TrackList tracks={playListTracks} isRemoval={true} onRemove={onRemove} />
</div>
</>
);
};
export default PlayList;
到目前为止,您应该已经观察到SearchResults
和PlayList
导入的组件TrackList
。
创建曲目列表组件
该组件包含Track
映射到轨道列表的每个项目的组件。/components/TrackList.js
将包含以下代码:
import React from 'react'
import Track from './Track'
import Img from '../assets/omo.png'
const TrackList = ({ tracks, onAdd, isRemoval, onRemove }) => {
return (
<>
{(tracks.length > 0) &&
<div className="playList">
{tracks.map((track) => {
return (
<Track key={track.id} track={track} onAdd={onAdd} isRemoval={isRemoval} onRemove={onRemove} />
)
})}
</div >
}
{(tracks.length === 0) &&
<div className="playList">
<img src={Img} alt="Oops!" />
<h3>Oops! No Tracks founds</h3>
<p>Search and add for a track</p>
</div>
}
</>
)
}
export default TrackList
创建轨道组件
此组件接受曲目数据作为对象,并在 中呈现 Spotify 播放器<iframe>
。它还包含一个 TrackAction,允许用户从曲目列表中添加或删除曲目。/components/Track.js
将包含以下代码:
import React, { useState, useEffect } from 'react'
import bgImg from '../assets/justin.PNG'
const Track = ({ track, onAdd, onRemove, isRemoval }) => {
const [trackBg, setTrackBg] = useState('')
useEffect(() => {
track.image? setTrackBg(track.image) : setTrackBg(bgImg)
}, [track.image])
const addTrack = () => onAdd(track)
const removeTrack = () => onRemove(track)
return (
<ul className="track">
<li>
<div>
<div className="item" >
<div>
<h3>{track.name}</h3>
{track.artist} | {track.album}
</div>
{
onAdd || onRemove ?
<TrackAction isRemoval={isRemoval} removeTrack={removeTrack} addTrack={addTrack} />
:
""
}
</div>
</div>
</li>
<li>
<iframe src={"https://open.spotify.com/embed/track/" + track.id} width="100%" height="80" frameBorder="0" allowtransparency="True" allow="encrypted-media" title="preview" />
</li>
</ul>
)
}
const TrackAction = ({ isRemoval, removeTrack, addTrack }) => {
return (
<>
{
isRemoval ?
<button className="btn" onClick={removeTrack}> - </button>
:
<button className="btn" onClick={addTrack}> + </button>
}
</>
)
}
export default Track
创建用户的播放列表收藏页面
该组件包含用户保存到 Fauna 数据库的所有播放列表的列表。
该getPlaylists
函数获取经过身份验证的用户创建的所有播放列表。
播放列表曲目默认是隐藏的,直到用户点击特定的播放列表,然后该togglePlaylist
函数将点击的播放列表设置为活动状态,然后呈现属于活动播放列表的曲目。
该removePlaylist
函数获取播放列表的 ID 并将其从数据库中删除。/components/MyCollections.js
将包含以下代码:
import React, { useState, useEffect } from "react";
import NavBar from "./NavBar";
import { useHistory } from "react-router-dom";
import { deletePlaylist, getPlaylists } from "../utils/model";
import bgImg from '../assets/justin.PNG'
import Track from './Track'
const MyCollections = () => {
const history = useHistory();
const [userData, setUserData] = useState(JSON.parse(localStorage.getItem("user")));
const [playlists, setPlaylists] = useState([])
const [activePlaylist, setactivePlaylist] = useState()
useEffect(() => {
if (!localStorage.getItem("user")) {
history.push("/");
}
getPlaylists(userData?.user_id)
.then(req => {
return setPlaylists(req)
})
.catch((err) => console.log(err.message))
if (!userData) {
setUserData(JSON.parse(localStorage.getItem("user")))
}
}, [userData, history]);
const togglePlaylist = (id) => {
if (activePlaylist === id) {
setactivePlaylist()
}
else {
setactivePlaylist(id)
}
}
const removePlaylist = (playlist) => {
deletePlaylist(playlist.id)
.then(req => {
const newPlaylist = playlists.filter((list) => list.id !== playlist.id)
playlists.unshift(playlist)
return setPlaylists(newPlaylist)
})
.catch((err) => console.log(err.message))
}
return (
<>
<NavBar userData={userData} />
<div className="container">
<h1 >
My Collections
</h1>
<article className="section">
<div className="trackList">
<div className="playList">
{playlists.length ?
playlists?.map((playlist) => { return (
<ul className="track" key={playlist.id}>
<li onClick={() => togglePlaylist(playlist.id)}>
<div >
<div className="item" >
<div>
<h3>{playlist.name}</h3>
</div>
<button className="btn" onClick={(e) => {
e.preventDefault()
removePlaylist(playlist)
}}> Delete </button>
</div>
</div>
</li>
{activePlaylist === playlist.id &&
<div >
{playlist.tracks.map((track) => {
return (
<Track
key={track.id}
track={track}
/>
)})}
</div>
}
</ul>
)
})
:
<h2 >No Playlist saved . . .</h2>
}
</div>
</div>
</article>
</div>
</>
);
};
export default MyCollections;
这样设置好组件后,我们的应用程序应该可以正常运行了。
我们不会就此止步。注意:如果我们访问了未定义的路由,就会出现错误。这是因为我们必须创建一个错误处理组件。
错误处理
我们将创建一个组件,当用户转到我们未定义的任何路线时,该组件将被渲染。/components/Error.js
将包含以下代码:
import React from 'react'
import { Link } from 'react-router-dom'
const Error = () => {
return (
<div >
<h1> Oops! Page Not found. </h1>
<h3><Link to="/create">Go back to safety</Link></h3>
</div>
)
}
export default Error
结论
在成功创建了此应用程序并将 Fauna 和 Spotify 集成到 React 中后,我们学习了如何使用 Spotify Web API 进行无需邮箱地址和密码的用户身份验证,以及如何使用 Fauna 数据库存储用户数据。我们还探索了 Spotify Web API 的搜索端点,以及如何在使用 Fauna 数据库作为存储介质的情况下处理来自 API 的请求和响应。
您可以从我的GitHub 仓库下载该应用程序的源代码,或点击此处访问演示。您也可以通过Twitter联系我。
与“与动物一起写作”计划相关而撰写。
文章来源:https://dev.to/wolzcodelife/how-to-build-a-music-playlist-with-react-spotify-and-fauna-40k6