Next.js 13 - Idiomatic access of route params on edge API functions - next.js

I'm trying to understand more about dynamic APIs with Next.
Specifically, I'm working on an Edge functions w/ native web APIs. ^1
The documentation for next handlers suggests the idiomatic way to get the dynamic component from a route is to use req.query. ^2
query does't exist on a Node request, however.
// pages/api/user/[id].ts
import { jsonResponse } from "src/utils/jsonResponse";
export const config = {
runtime: 'edge',
}
export default async function handler(
req: Request,
) {
const {id} = req.query // doesn't work because query isn't on the request
}
This makes sense to me since the id isn't actually a query parameter, but part of the route.
So, now I'm on a standard web question I guess, but in the context of Next and I'm curious if there's an idiomatic way to do this.
Right now, my solution is:
// pages/api/user/[id].ts
import { jsonResponse } from "src/utils/jsonResponse";
export const config = {
runtime: 'edge',
}
export default async function handler(
req: Request,
) {
const { searchParams, pathname, } = new URL(req.url)
const parts = pathname.split('/')
const id = parts.pop();
}

Related

RTK Query and Mutation sequentially

I have a situation using rtk about how to use the mutations and queries sequentially.
In my use case, in a route like /status/:id/:version, I need to create a job based on Id and version and then monitor the progress of creation (takes around 30 seconds). I have a query and a mutation in this route
const {id, version} = useParams()
const pollRef = useRef(1000)
const [createJob, {data: postData, error, isSuccess}] = useCreateJobMutation()
const {data, ...} = useGetJobIdQuery(postData[0].id, { pollingInterval: pollRef.current })
if (data.progress === 100) {
pollRef.current = 0 // stop polling GET route
use data ...
}
useEffect(()=> {
createJob(newJob) // newJob is created based on id, version
}, [])
I need to wait for the postData to be valid (not undefined), the issue is how to send the result of the mutation to the query without violating the hook rules. (I get ERROR Rendered more hooks during the previous render.)
if (isSuccess) {
useGetJobIdQuery(...) // violate hook rules
}
useCreateJobMutation() and useGetJobIdQuery() work fine standanlone but not together
You can skip queries:
import { skipToken } from '#reduxjs/toolkit/query/react'
useGetJobIdQuery(isSuccess ? jobId : skipToken)

How to use runtime config in composable?

I want to do this
composables/apiFetch.ts
import { $fetch } from 'ohmyfetch'
export const useApiFetch = $fetch.create({ baseURL: useRuntimeConfig().apiUrl })
And use it within Pinia so I don't repeat myself writing $fetch.create over and over again for every single API call.
somewhere_in_pinia.ts
...TRIM...
actions: {
async doSomething(payload: SomeNicePayload): Promise<void> {
const response = await useApiFetch('/something', { method: 'POST', body: payload })
}
}
...TRIM...
But Nuxt won't allow me
[nuxt] [request error] nuxt instance unavailable
at useNuxtApp (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:472:13)
at Module.useRuntimeConfig (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:480:10)
at $id_Yl353ZXbaH (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:38358:90)
at async __instantiateModule__ (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:40864:3)
I have been looking for solution online, followed instruction from the official discussion to no avail.
EDIT
I don't want to use Nitro, since my backend is already written on Laravel. I need to access the host without re-typing it all over the place so I thought I could use .env and runtimeConfig.
you are trying to access Nuxt instance while it's not ready yet. To make it work, write your composable as a function :
import { $fetch } from 'ohmyfetch'
export const useApiFetch = (url, params) => {
const instance = $fetch.create({ baseURL: useRuntimeConfig().apiUrl })
return instance(url, params)
}

How To Unit Test NextJS API Route That Uses Repository Pattern?

I am new prisma / nextjs user and I am trying to understand how to unit test an API route that uses prisma. I have read the unit testing guide.
I like the dependency injection approach and have started trying to implement it. However I am struggling with the following development issue. Can anybody help?
With the dependency injection approach the unit testing guide explains how to setup the mock context and use this in the data access layer. Does anyone have any examples of how and where the real context could be initialised and used with an API route that uses a repository pattern? Is it possible to expand the next.js api handler with middleware to include the context to facilitate testing?
import type { NextApiRequest, NextApiResponse } from 'next'
import { PublishRepository } from '../../../repository'
// PUT /api/publish/:id
export default async function handle(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method == 'PUT') {
const postId = req.query.id;
let repo = new PublishRepository( // where does the live context come from and how is it initialised??? )
const post = repo.set_published(postId)
res.json(post);
}
}
Repository - Initialised using Context instance - How is this initialise for development and how is it mocked?
import { Post, PrismaClient } from "#prisma/client"
import { Context } from "../context"
import prisma from "lib/prisma"
export class PublishRepository {
private prisma: PrismaClient
constructor(context: Context) {
this.prisma = context.prisma
}
async set_published(post_id: string | string[]): Promise<Post> {
return await prisma.post.update({
where: { id: Number(post_id) },
data: { published: true },
});
}
}

Get values from SvelteKit's $app/stores outside of the lifecycle of a component

My Svelte components import readable stores like this:
import { classes, locations, schedule } from 'stores.ts'
In stores.ts, I want to build the URL for fetch dynamically using page.host from $app/stores.
// Note: this is not a Svelte component; it's stores.ts
import { readable } from 'svelte/store'
import { getStores } from '$app/stores'
const { page } = getStores()
let FQDN
page.subscribe(({ host }) => {
FQDN = host
})
const getArray = async (url) => {
const response: Response = await fetch(url)
if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
return await response.json()
}
const getReadableStore = (url: string) => readable([], set => {
getArray(`http://${FQDN}${url}`)
.then(set)
.catch(err => console.error('Failed API call:', err))
return () => {}
})
export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')
The sixth line throws this error...
Error: Function called outside component initialization
at get_current_component (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:652:15)
at Proxy.getContext (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:685:12)
at Module.getStores (/.svelte-kit/dev/runtime/app/stores.js:17:26)
at eval (/src/stores.ts:6:38)
at instantiateModule (/Users/nates/dev/shy-svelte/node_modules/#sveltejs/kit/node_modules/vite/dist/node/chunks/dep-e9a16784.js:68197:166)
Two questions...
What is the correct way to get page values from $app/stores outside of the context of a component? Is this possible? Answer from below: No, this is not possible outside the context of a component.
If I'm accessing a SvelteKit site, let's say http://localhost:3000/something or https://example.com and a Svelte component loads a readable store from stores.ts, is there a way in stores.ts to determine whether the original page request that loaded the component (which loaded from stores.ts) was http or https? Answer from below: No, this is not possible in stores.ts - only from a component.
UPDATE: Based on the feedback, I'm going to set a value in my .env called VITE_WEB_URL=http://localhost:3000 and change it for the production system. This cuts down on the number of lines of code and may be a better practice (comments welcome)...
// revised stores.ts
import { readable } from 'svelte/store'
const { VITE_WEB_URL } = import.meta.env
const getArray = async (url) => {
const response: Response = await fetch(url)
if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
return await response.json()
}
const getReadableStore = (url: string) => readable([], set => {
getArray(`${VITE_WEB_URL}${url}`)
.then(set)
.catch(err => console.error('Failed API call:', err))
return () => {}
})
export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')
Extract from https://kit.svelte.dev/docs#modules-$app-stores
Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with getContext.
Therefore, since the readable store is bound to the context of a svelte component, I suggest you subscribe either way ($ or .subscribe) inside the component of the SvelteKit website and then send the protocol value (http or https) as parameter when it updates so that stores.ts stores it in a variable.
However, it looks like SvelteKit does not provide the protocol value, so parse the client side window.location.href in the page subscription and then send it.
Referencing a svelte store can be done everywhere.
Using the $: shorthand syntax, however, only works within a component.
$: BASE = `http://${$page.host}`
SvelteKit appears to delegate this to fetch indeed

NextJS special characters routes do not work from browser

Using NextJS, I am defining some routes in getStaticPaths by making an API call:
/**
* #dev Fetches the article route and exports the title and id to define the available routes
*/
const getAllArticles = async () => {
const result = await fetch("https://some_api_url");
const articles = await result.json();
return articles.results.map((article) => {
const articleTitle = `${article.title}`;
return {
params: {
title: articleName,
id: `${article.id}`,
},
};
});
};
/**
* #dev Defines the paths available to reach directly
*/
export async function getStaticPaths() {
const paths = await getAllArticles();
return {
paths,
fallback: false,
};
}
Everything works most of the time: I can access most of the articles, Router.push works with all URLs defined.
However, when the article name includes a special character such as &, Router.push keeps working, but copy/pasting the URL that worked from inside the app to another tab returns a page:
An unexpected error has occurred.
In the Network tab of the inspector, a 404 get request error (in Network) appears.
The component code is mostly made of API calls such as:
await API.put(`/set_article/${article.id}`, { object });
With API being defined by axios.
Any idea why it happens and how to make the getStaticPaths work with special characters?
When you transport values in URLs, they need to be URL-encoded. (When you transport values in HTML, they need to be HTML encoded. In JSON, they need to be JSON-encoded. And so on. Any text-based system that can transport structured data has an encoding scheme that you need to apply to data. URLs are not an exception.)
Turn your raw values in your client code
await API.put(`/set_article/${article.id}`)
into encoded ones
await API.put(`/set_article/${encodeURIComponent(article.id)}`)
It might be tempting, but don't pre-encode the values on the server-side. Do this on the client end, at the time you actually use them in a URL.

Resources