Learn how to create a database with LeanCloud and an application with a server with Next.js.
foreword
Through this tutorial you will learn:
- Use LeanCloud as a free database
- Develop an application with front and back ends using Next.js
- Publish the app to Vercel
- Easy styling with Tailwind
We will create an application for scoring film and television dramas, I will deploy it in rec.zehao.me , and I will put the complete source code in 2eha0/records
Create a Next.js application
Create a project using the Next.js official template
& npx create-next-app --example with-tailwindcss my-app
The goal has already configured the following for you:
- Next.js latest version
- TypeScript
- Tailwind CSS & Automatically strip unused class names
- Next.js API Routing Example
Create front-end components
Now we can start creating components, pages/index.tsx
is the entry file of the application, let's modify the overall layout first
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
const Home: NextPage = () => {
return (
<div className='mx-[3.5rem] min-w-[15rem] max-w-full sm:mx-auto sm:w-[30rem] font-sans'>
<Head>
<title>我看过的</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className='text-slate-300 flex justify-between items-center text-xl sm:text-5xl my-8 sm:my-20'>
<span>我看过的</span>
<span className='text-xs sm:text-xl'>电影 / 动漫 / 剧 / 书</span>
</h1>
</div>
)
}
export default Home
Next, we need to add a card component to the application to display the information of film and television works, create a new components/card.tsx
file
// components/card.tsx
import Image from 'next/image'
export const Card: React.FC<Props> = (props) => {
return (
<section className='relative before:content-[""] before:border-l-2 before:absolute before:inset-y-0 before:-left-9 before:translate-x-[0.44em] pb-10 first:before:top-1 last:before:bottom-10'>
<p className='text-slate-400 text-xs mb-2 sm:text-base sm:mb-3 relative'>
2022/04/02
<i className='absolute w-4 h-4 rounded-full bg-slate-200 -left-9 top-1/2 translate-y-[-50%]' />
</p>
<div className="flex items-start">
<div className="flex-1 mr-2">
<p className='text-md mb-2 sm:text-2xl sm:mb-3 leading-6 text-slate-900'>
鬼灭之刃
<span className='text-slate-400'>(2019)</span>
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>评分:</span>
<big className='font-bold text-blue-500'>🤔 还行</big>
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>分类:</span>
动漫
</p>
<div className="bg-white text-xs text-slate-500 leading-2 mt-4 sm:text-base">
老套的升级打怪式剧情,但动画制作的质量还不错,适合下饭
</div>
</div>
<div className='flex-none w-1/6 rounded-md sm:w-[5rem] sm:rounded-xl overflow-hidden bg-slate-100 relative aspect-[85/113]'>
<Image
src='https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2551222097.webp'
layout='fill'
objectFit="cover"
alt='鬼灭之刃'
className="hover:opacity-75 duration-300 ease-in-out"
/>
</div>
</div>
</section>
)
}
We used the next/image
component for the picture, we need to modify the next.config.js
file and add the picture domain name configuration
// next.config.js
module.exports = {
reactStrictMode: true,
images: {
domains: [
'img1.doubanio.com',
'img2.doubanio.com',
'img3.doubanio.com',
'img9.doubanio.com',
],
},
}
Then we can add the <Card />
component to pages/index.tsx
to see the effect
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import { Card } from '../components/card'
const Home: NextPage = () => {
return (
<div className='mx-[3.5rem] min-w-[15rem] max-w-full sm:mx-auto sm:w-[30rem] font-sans'>
<Head>
<title>我看过的</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className='text-slate-300 flex justify-between items-center text-xl sm:text-5xl my-8 sm:my-20'>
<span>我看过的</span>
<span className='text-xs sm:text-xl'>电影 / 动漫 / 剧 / 书</span>
</h1>
<div>
<Card />
<Card />
<Card />
</div>
</div>
)
}
export default Home
So far, the appearance of the application has taken shape. Next, we add some props
for Card
cf888f2dff3b42b33a14fd99d5f9cc89---. First, let's define the type of props
, we are in the root directory Create a new types.ts
file
// types.ts
export type Record = {
date: string
title: string
score: 1 | 2 | 3 | 4 | 5
comment?: string
year: number
img: string
type: 'movie' | 'tv' | 'anime' | 'book'
}
The reason why it is placed in the root directory is that this type will also be used when creating an API later.
Next, let's modify the Card
component and replace the data part with props
// components/card.tsx
import Image from 'next/image'
import { Record } from '../types'
type Props = Record
const Score: React.FC<Pick<Props, 'score'>> = ({ score }) => {
switch (score) {
case 1:
return <big className='font-bold text-black-500'>😡 不看也罢</big>
case 2:
return <big className='font-bold text-green-500'>🥱 无聊</big>
case 3:
return <big className='font-bold text-blue-500'>🤔 还行</big>
case 4:
return <big className='font-bold text-violet-500'>🤩 值得一看</big>
case 5:
return <big className='font-bold text-orange-500'>💯 神作!</big>
}
}
const renderType = (type: Props['type']) => {
const typeMap = {
movie: '电影',
tv: '剧',
book: '书',
anime: '动漫',
}
return typeMap[type] ?? '未知'
}
export const Card: React.FC<Props> = (props) => {
return (
<section className='relative before:content-[""] before:border-l-2 before:absolute before:inset-y-0 before:-left-9 before:translate-x-[0.44em] pb-10 first:before:top-1 last:before:bottom-10'>
<p className='text-slate-400 text-xs mb-2 sm:text-base sm:mb-3 relative'>
{ new Date(props.date).toLocaleDateString() }
<i className='absolute w-4 h-4 rounded-full bg-slate-200 -left-9 top-1/2 translate-y-[-50%]' />
</p>
<div className="flex items-start">
<div className="flex-1 mr-2">
<p className='text-md mb-2 sm:text-2xl sm:mb-3 leading-6 text-slate-900'>
{ props.title }
<span className='text-slate-400'>({props.year})</span>
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>评分:</span>
<Score score={ props.score } />
</p>
<p className='text-xs sm:text-base text-slate-700'>
<span className='text-slate-400'>分类:</span>
{ renderType(props.type) }
</p>
<div className="bg-white text-xs text-slate-500 leading-2 mt-4 sm:text-base">
{ props.comment }
</div>
</div>
<div className='flex-none w-1/6 rounded-md sm:w-[5rem] sm:rounded-xl overflow-hidden bg-slate-100 relative aspect-[85/113]'>
<Image
src={ props.img }
layout='fill'
objectFit="cover"
alt={ props.title }
className="hover:opacity-75 duration-300 ease-in-out"
/>
</div>
</div>
</section>
)
}
Set up LeanCloud Storage
LeanCloud is a BaaS (Backend as a Service)^ Backend as a Service: [Backend as a Service ] platform, it is recommended to register the international version of LeanCloud , which can be exempted from real-name authentication
First, we need to create a Class in Data Storage
- Name the Class
Records
- Add
img
,title
,type
,comment
andtype
fields, their types areString
- Add
year
,score
fields and set their type toNumber
Create read data API
Now let's create an API for reading data from LeanCloud
First we need to install the LeanCloud JS SDK
$ npm install leancloud-storage --save
Then we need to add the configuration information of LeanCloud to .env.local
, the configuration information can be found in "Settings" -> "App keys"
LEANCLOUD_APP_ID="{replace-your-app-id}"
LEANCLOUD_APP_KEY="{replace-to-your-app-key}"
LEANCLOUD_SERVER_URL="{replace-to-your-server-url}"
New pages/api/records.ts
// pages/api/records.ts
import AV from 'leancloud-storage'
import { Record } from '../../types'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse<Record[]>
) {
try {
const {
LEANCLOUD_APP_ID: appId,
LEANCLOUD_APP_KEY: appKey,
LEANCLOUD_SERVER_URL: serverURL,
} = process.env
if (!appId || !appKey || !serverURL) {
res.status(500).json({ error: 'Missing Leancloud config' } as any)
return
}
AV.init({ appId, appKey, serverURL })
const query = new AV.Query('Record')
const data = await query.find()
const records: Record[] = data.reverse().map(x => {
const json = x.toJSON()
return {
date: json.createdAt,
title: json.title,
score: json.score,
comment: json.comment,
year: json.year,
img: json.img,
type: json.type,
}
})
res.status(200).json(records)
} catch (e: any) {
res.status(500).json(e)
}
}
Then we modify pages/index.tsx
and get data from /api/records
interface
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import { useEffect, useState } from 'react'
import { Card } from '../components/card'
import { Record } from '../types'
const Home: NextPage = () => {
const [ records, setRecords ] = useState<Record[] | null>(null)
useEffect(() => {
fetch('/api/records')
.then(res => res.json())
.then(setRecords)
}, [])
const renderCards = () => {
if (!records) {
return null
}
return records.map(x => <Card key={ `${x.date}${x.title}${x.year}` } { ...x } />)
}
return (
<div className='mx-[3.5rem] min-w-[15rem] max-w-full sm:mx-auto sm:w-[30rem] font-sans'>
<Head>
<title>我看过的</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className='text-slate-300 flex justify-between items-center text-xl sm:text-5xl my-8 sm:my-20'>
<span>我看过的</span>
<span className='text-xs sm:text-xl'>电影 / 动漫 / 剧 / 书</span>
</h1>
<div>
{ renderCards() }
</div>
</div>
)
}
export default Home
Deploy to Vercel
Our application is ready to run locally, let's deploy it to Vercel next.
- Commit our code to a git repository (eg Github, GitLab, BitBucket)
- Import the Next.js project into Vercel
- Setting environment variables during import
- Click "Deploy"
Vercel will automatically detect that you are using Next.js and enable the correct settings for your deployment. Finally, your application is deployed at a URL like xxx.vercel.app.
adding data
Now that our application is running on the public network, we can try to add a few pieces of data on LeanCloud, and then refresh the page to see if it can be displayed normally.
Summarize
In this tutorial, we were able to create a Next.js application that displays a list of data dynamically fetched from LeanCloud with Tailwind CSS beautification.
This article is reproduced from my blog https://www.zehao.me/full-stack-app-nextjs-leancloud-tailwind/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。