- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
目前,我的后端在 NodeJS (Express) 中,并且目前正在从前端的 create-react-app 迁移到 NextJS。我应该从 NextJS 向快速服务器发出 API 请求,还是应该将 API 移动到 NextJS 本身(NextJS 直接与数据库交互)?在NextJS中编写API组件(与DB交互的逻辑)是否正常?或者我应该采用 NextJS 与我的旧 express 服务器交互的更传统的方法吗?
从长远来看,这两种方法中哪种更好(我知道在页面加载速度方面,在 NextJS 中编写 API 组件更快,但会高度耦合我的前端和后端逻辑):
NextJS 与编写在单独后端服务器(Express 服务器)中的 API 交互
将部分/全部后端逻辑移至 NextJS,NextJS 直接与数据库交互
最佳答案
您绝对应该使用无服务器 Node api 路由。 nextjs用数据库很正常。 pages/api/**
中的无服务器 api 路由与 ./utils 或 ./lib 中的服务器端函数(命名这些目录的任意首选项)是分区逻辑的绝佳组合。例如,考虑我正在为当前使用 booksy 作为 CMS 的客户处理的项目中的文件中的以下内容。我不得不对他们的网络身份验证流程和 API 路由进行逆向工程,以持续远程提供数据。
import type { BooksyAuthResponse } from '@/types/index';
import { format } from 'date-fns';
import { parseUrl } from './helpers';
const EMAIL = process.env.BOOKSY_BIZ_EMAIL ?? "";
const PASSWORD = process.env.BOOKSY_BIZ_PASSWORD ?? "";
const API_KEY = process.env.NEXT_PUBLIC_BOOKSY_BIZ_API_KEY ?? "";
const FINGERPRINT =
process.env.NEXT_PUBLIC_BOOKSY_BIZ_X_FINGERPRINT ?? "";
export const getAccessToken =
async (): Promise<BooksyAuthResponse> => {
const response = await fetch(
`https://us.booksy.com/api/us/2/business_api/account/login?x-api-key=${API_KEY}&x-fingerprint=${FINGERPRINT}`,
{
method: 'POST',
headers: {
'X-Api-Key': API_KEY,
'X-Fingerprint': FINGERPRINT,
'Content-Type': 'application/json',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36',
Connection: 'keep-alive',
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br'
},
keepalive: true,
body: JSON.stringify({
email: EMAIL!,
password: PASSWORD!
})
}
);
const authData = await response.json();
return authData;
};
// consumed in getStaticProps of pages/index.tsx
export const getLatestBooksyReviews = async ({
reviewsPerPage,
pageIndex
}: BooksyPagination): Promise<Response> => {
const { access_token } = await getAccessToken();
pageIndex = 1;
reviewsPerPage = 10;
return fetch(
`https://us.booksy.com/api/us/2/business_api/me/businesses/481001/reviews/?reviews_page=${pageIndex}&reviews_per_page=${reviewsPerPage}`,
Object.freeze({
headers: {
'X-Api-key': API_KEY,
'X-Access-Token': `${access_token}`,
'X-fingerprint': FINGERPRINT,
Authorization: `s-G1-cvdAC4PrQ ${access_token}`!,
'Cache-Control':
'public, s-maxage=86400, stale-while-revalidate=43200',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36',
Connection: 'keep-alive',
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br'
},
method: 'GET',
keepalive: true
} as const)
);
};
然后您可以在无服务器页面/api 路由中使用 getAccessToken
函数以用于客户端获取
import { NextApiRequest, NextApiResponse } from 'next';
import { BooksyReviewFetchResponse } from '@/types/booksy';
import fetch from 'isomorphic-unfetch';
import { getAccessToken } from '@/lib/booksy';
const API_KEY = process.env.NEXT_PUBLIC_BOOKSY_BIZ_API_KEY ?? '';
const FINGERPRINT =
process.env.NEXT_PUBLIC_BOOKSY_BIZ_X_FINGERPRINT ?? '';
export default async function (
req: NextApiRequest,
res: NextApiResponse<BooksyReviewFetchResponse>
) {
// console.log(req.headers);
const {
query: { reviews_page, reviews_per_page }
} = req;
const { access_token } = await getAccessToken();
const rev_page_number = reviews_page ? reviews_page : 1;
const reviews_pp = reviews_per_page ? reviews_per_page : 10;
const response = await fetch(
`https://us.booksy.com/api/us/2/business_api/me/businesses/481001/reviews/?reviews_page=${rev_page_number}&reviews_per_page=${reviews_pp}`,
{
headers: {
'X-Api-key': API_KEY,
'X-Access-Token': `${access_token}`,
'X-fingerprint': FINGERPRINT,
Authorization: `s-G1-cvdAC4PrQ ${access_token}`,
'Cache-Control':
's-maxage=86400, stale-while-revalidate=43200',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36',
Connection: 'keep-alive',
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br'
},
method: 'GET',
keepalive: true
}
);
// console.log(response.headers);
const booksyReviews: BooksyReviewFetchResponse =
await response.json();
res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=43200'
);
return res.status(200).json(booksyReviews);
}
首先,使用 pages/index.tsx 中 getStaticProps(服务器端)中的 getLatestBooksyReviews
函数(参见 const initialData
)。这是将在默认导出中在客户端使用无服务器 booksy 函数的同一个文件
export async function getStaticProps(
ctx: GetStaticPropsContext
): Promise<
GetStaticPropsResult<{
other: LandingDataQuery['other'];
popular: LandingDataQuery['popular'];
places: LandingDataQuery['Places'];
merchandise: LandingDataQuery['merchandise'];
businessHours: LandingDataQuery['businessHours'];
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
initDataGallery: Partial<
Configuration<Gallery, any, Fetcher<Gallery>>
>;
initialData: Partial<
Configuration<
BooksyReviewFetchResponse,
any,
Fetcher<BooksyReviewFetchResponse>
>
>;
}>
> {
const apolloClient = initializeApollo(
{ headers: ctx.params } ?? {}
);
await apolloClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
}
});
await apolloClient.query<
LandingDataQuery,
LandingDataQueryVariables
>({
query: LandingDataDocument,
variables: {
other: WordPress.Services.Other,
popular: WordPress.Services.Popular,
path: Google.PlacesPath,
googleMapsKey: Google.MapsKey
}
});
const dataGallery = await getLatestBooksyPhotos();
const initDataGallery: Gallery = await dataGallery.json();
const dataInit = await getLatestBooksyReviews({
reviewsPerPage: 10,
pageIndex: 1
});
const initialData: BooksyReviewFetchResponse =
await dataInit.json();
return addApolloState(apolloClient, {
props: { initialData, initDataGallery },
revalidate: 600
});
}
现在,getStaticProps 在服务器端返回的 Prop 通过默认导出在客户端无缝使用,它作为内容的初始数据注入(inject),在第一次加载时立即在生产中显示。然后,客户端在重新验证时失效 (SWR) Hook 从无服务器函数中获取用于分页的附加数据。这创造了一个无缝的用户体验。它缓存数据,提供近乎即时的体验
const { data } =
useSWR<BooksyReviewFetchResponse>(
() =>
`/api/booksy-fetch?reviews_page=${reviews_page}&reviews_per_page=${reviews_per_page}`,
fetcher,
initialData
);
export default function Index<T extends typeof getStaticProps>({
other,
popular,
Header,
Footer,
merchandise,
places,
businessHours,
initialData,
initDataGallery
}: InferGetStaticPropsType<T>) {
const GalleryImageLoader = ({
src,
width,
quality
}: ImageLoaderProps) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
const reviews_per_page = 10;
const [reviews_page, set_reviews_page] = useState<number>(1);
const page = useRef<number>(reviews_page);
const { data } =
useSWR<BooksyReviewFetchResponse>(
() =>
`/api/booksy-fetch?reviews_page=${reviews_page}&reviews_per_page=${reviews_per_page}`,
fetcher,
initialData
);
const { data: galleryData } = useSWR<Gallery>(
'/api/booksy-images',
fetcherGallery,
initDataGallery
);
// total items
const reviewCount = data?.reviews_count ?? reviews_per_page;
// total pages
const totalPages =
(reviewCount / reviews_per_page) % reviews_per_page === 0
? reviewCount / reviews_per_page
: Math.ceil(reviewCount / reviews_per_page);
// correcting for array indeces starting at 0, not 1
const currentRangeCorrection =
reviews_per_page * page.current - (reviews_per_page - 1);
// current page range end item
const currentRangeEnd =
currentRangeCorrection + reviews_per_page - 1 <= reviewCount
? currentRangeCorrection + reviews_per_page - 1
: currentRangeCorrection +
reviews_per_page -
(reviewCount % reviews_per_page);
// current page range start item
const currentRangeStart =
page.current === 1
? page.current
: reviews_per_page * page.current - (reviews_per_page - 1);
const pages = [];
for (let i = 0; i <= reviews_page; i++) {
pages.push(
data?.reviews ? (
<BooksyReviews pageIndex={i} key={i} reviews={data.reviews}>
<nav aria-label='Pagination'>
<div className='hidden sm:block'>
<p className='text-sm text-gray-50'>
Showing{' '}
<span className='font-medium'>{`${currentRangeStart}`}</span>{' '}
to{' '}
<span className='font-medium'>{`${currentRangeEnd}`}</span>{' '}
of <span className='font-medium'>{reviewCount}</span>{' '}
reviews (page:{' '}
<span className='font-medium'>{page.current}</span> of{' '}
<span className='font-medium'>{totalPages}</span>)
</p>
</div>
<div className='flex-1 inline-flex justify-between sm:justify-center my-auto'>
<button
disabled={page.current - 1 === 0 ? true : false}
onClick={() => set_reviews_page(page.current - 1)}
className={cn('landing-page-pagination-btn', {
' cursor-not-allowed bg-redditSearch':
reviews_page - 1 === 0,
' cursor-pointer': reviews_page - 1 !== 0
})}
>
Previous
</button>
<button
disabled={page.current === totalPages ? true : false}
onClick={() => set_reviews_page(page.current + 1)}
className={cn('landing-page-pagination-btn', {
' cursor-not-allowed bg-redditSearch':
reviews_page === totalPages,
' cursor-pointer': reviews_page < totalPages
})}
>
Next
</button>
</div>
</nav>
</BooksyReviews>
) : (
<ReviewsSkeleton />
)
);
}
useEffect(() => {
(async function Update() {
return (await page.current) === reviews_page
? true
: set_reviews_page((page.current = reviews_page));
})();
}, [page.current, reviews_page]);
return (
<>
<AppLayout
title={'The Fade Room Inc.'}
Header={Header}
Footer={Footer}
>
{galleryData?.images ? (
<Grid>
{galleryData.images
.slice(6, 9)
.map((img, i) => {
<GalleryCard
key={img.image_id}
media={galleryData}
imgProps={{
loader: GalleryImageLoader,
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540
}}
/>;
})
.reverse()}
</Grid>
) : (
<LoadingSpinner />
)}
{galleryData?.images ? (
<Marquee variant='secondary'>
{galleryData.images
.slice(3, 6)
.map((img, j) => (
<GalleryCard
key={img.image_id}
media={galleryData}
variant='slim'
imgProps={{
loader: GalleryImageLoader,
width: j === 0 ? 320 : 320,
height: j === 0 ? 320 : 320
}}
/>
))
.reverse()}
</Marquee>
) : (
<LoadingSpinner />
)}
<LandingCoalesced
other={other}
popular={popular}
places={places}
businessHours={businessHours}
merchandise={merchandise}
>
{data?.reviews ? (
<>
<>{pages[page.current]}</>
<span className='hidden'>
{
pages[
page.current < totalPages
? page.current + 1
: page.current - 1
]
}
</span>
</>
) : (
<ReviewsSkeleton />
)}
</LandingCoalesced>
</AppLayout>
</>
);
}
Next.js 是一个全栈的前端框架
关于node.js - nextjs做一个API请求正常吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67685683/
我用运行 Node node --debug app OR node --debug-brk app 它有反应 debugger listening on port 5858 Express serv
这个问题在这里已经有了答案: What is the difference between (int *i) and (int* i) in context of both C and C++? [
我有一个应用程序,它通过消息队列将数据库写入命令分派(dispatch)给工作人员(数量非常大),因此无法保证它们的接收顺序。 我有两个 Node ,例如“Account”和“Media”。在此假设的
有没有办法在调用 ts-node 时将选项传递给 Node ?我正在尝试在 Node 中使用一个实验性功能,如果它能与 ts-node 一起使用,那就太好了。 这就是我目前正在做的事情: ts-nod
我有一个容器化的Node应用程序,它在DigitalOcean服务器上运行。当我更新服务器上的应用程序时,该应用程序必须关闭一小段时间。为了能够更新应用程序并避免停机,我目前正在阅读零停机时间部署/蓝
我正在编写一个 Node.js 应用程序。我正在使用 request 和 Cheerio 加载一组 URL 并获取该网站的大量信息,现在假设我想要获取的只是标题: var urls = {"url_1
如果不弹出以下错误,我无法安装任何 Node.js 模块。错误代码引用package.json文件。如果知道为什么会发生这种情况,我们将不胜感激。 最佳答案 这些不是错误,它们只是警告。一切都应该如此
如果我运行(从我的项目目录中): supervisor javascripts/index.js 我得到:/usr/bin/env: Node :没有这样的文件或目录 如果我运行: node java
我已遵循使用 Node-Inspector 的所有步骤 但是当我打开应用程序时,我在控制台上看不到任何脚本或日志。 我的应用程序在端口 4000 上运行。我认为唯一可能发生冲突的是端口 8080 上的
我在android中使用rxjava2,有时会遇到这样的问题: Observable.fromArray( // maybe a list about photo url in SD
我目前正在使用 Node 光纤来编写同步服务器端代码。我主要通过 try-catch block 进行错误处理,但外部库或其他小部分异步代码中总是有可能发生错误。我正在考虑使用新的域功能来尝试将这些错
看起来node-debug是node-inspector周围的一个shell?分别什么时候应该使用? 最佳答案 如果您安装node-debug,您只能访问node-debug命令。 如果您安装node
我目前正在代理后面工作,该代理不允许我执行此命令的 HTTP GET 请求阶段: Node node-sass/scripts/build.js 请求阶段: gyp http GET https://
听说node js可以用在服务端。我以前用过jsp。 jsp页面内部的java代码对客户端是不可见的。如果 Node js 只是 javascript,那么它如何对客户端不可见? 最佳答案 首先,No
我正在为 Node native 插件从 node-waf 构建迁移到 node-gyp 构建系统。 node-gyp 说它支持多个目标版本,但我在使用 node-gyp 时找不到如何指定目标 Nod
给定一个 $node ,我正在尝试在以下两种输出该 $node 的方式之间做出决定。 要么 $output = theme('node', $node); 或 node_build_content($
如果package.json中的窗口A打开一个新窗口B,node-main如何访问它?这是我的代码: package.json { "main": "index.html",
我试图在我的 xml 中的特定节点 ( ) 之前插入一个注释节点。这是它的方法: function test(xmlResponse) { var parser = new DOMParse
我正在尝试做npm install wrtc使用 Node 版本 16.14.0 但这还没有完成。它在给npm error code 1所以我试图将 Node 版本更改为以前的 lts 14.19.0
当我在 Visual Studio 中运行 Node.js 应用程序时,我收到以下消息:DeprecationWarning: 'node --debug' 和 'node --debug-brk'
我是一名优秀的程序员,十分优秀!