You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
More of a discussion (maybe we need a discussions tab). My understanding was that the API was to be used as a standalone API service rather then just a functions wrapper.
In most of our SaaS products the API is a seperate service. Currently apps/api there is no way to easily verify a user.
The solution I went with
In the interest of keeping strong seperation of concern I have setup the API as follows.
https://api.mydomain.com
Middleware
In the middleware for the API I do a few things.
exportfunctionmiddleware(request: NextRequest){// if someone tries to access this as a page it will redirect to the web urlif(request.nextUrl.pathname==='/'){returnNextResponse.redirect(env.NEXT_PUBLIC_WEB_URL,{status: 308// Permanent redirect});}// We always fetch on the server within the core app so securing routes with a simple api key is a fallback safeguardconstapiKey=request.headers.get('x-api-key');if(apiKey!==env.API_KEY){returnNextResponse.json({error: 'Unauthorized'},{status: 401});}}// This only enforces these checks in the /api folder. this means /webhooks runs without middlewareexportconstconfig={matcher: ['/','/api/:path*']}
Server Component/Page Fetching
How do we attach the user to the api?
Clerk has a few different methods but the easiest way I found was just to include the Auth header. I created a fetch function similar to this that I can call from any server component in app or web or any other microservice we create.
This attached the session to the auth header as Clerk expects it.
Server Actions/Forms
The intent for Server Actions becomes really obvious in this now. We can use server actions to invoke the API. Here is a very simple one I use for submitting a form.
in apps/app/app/actions/nameAction.ts
"use server"import{log}from"@repo/observability/log"import{serverFetch}from"@/lib/helpers"// name here is just a very simple body but you could add form validation and more complex dataexportasyncfunctioncreateGarageAction(name: string): Promise<MyResponse<boolean>>{constMY_ROUTE="https://api.domain.com/your/path"const{ data, error }=awaitserverFetch<DoriResponse<boolean>>(MY_ROUTE,{method: "POST",body: JSON.stringify({name})})if(error){log.error(error)return{data: null,error: "Failed to create garage"}}return{data: true,error: null}}
Clerk In API
After this we can create the user session on the server.
If you use the JWT public key it will verify the session without sending a request to Clerks backend. I have created a function like so.
exportconstgetValidClerkSession=async(req: NextRequest): Promise<{user: UserSelect,context: UserMinContext}>=>{constclerk=awaitclerkClient()constauth=awaitclerk.authenticateRequest(req,{jwtKey: env.CLERK_JWT_KEY,authorizedParties: [WEB,API,APP],// these are the urls for that specific environmnet});if(!auth.isSignedIn){thrownewAuthorizationError(`Unauthorized - ${auth.reason||'Please sign in'}`);}const{userId: clerkId}=awaitauth.toAuth()// here I get some more info about the user that can be used in any api route that requires a user but this is not neededconstuserRepository=newUserRepository(database)constuser=awaituserRepository.getUserByClerkId(clerkId)if(!user){thrownewAuthorizationError(`User not found - ${clerkId}`)}constcontext: UserContext={clerkId: user.clerkId,userId: user.id,email: user.email,username: user.username||'',}// returning this is extendable and gives us all the info we might need on a base user in the api routereturn{ user, context }}
Then I just call this first in any API route that needs a user attached
This makes the API a completely stand alone system that can still use the Clerk user for auth.
With how Next.js works it adds a strong seperation of concern. It also makes it extendable or replaceable. They push heavy that data fetching/mutations should happen server side. This has the benefit's of both and prevents any valuable info being leaked to the client.
Shortcomings
I am aware that calling a server action in itself is an api request. In early testing in other projects the impact with not really a concern. The benefits out weighed the negatives.
While this is definitely more code, I think its one of the core reasons why this repo is so popular. Everything is seperate, it makes it very easy to find things.
This extends on that fact. It felt a bit messy putting API routes in the app when we had a dedicated API service.
Describe alternatives you've considered
I would love some feedback on this. Maybe it can be improved.
This discussion was converted from issue #323 on December 08, 2024 20:56.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Is your feature request related to a problem? Please describe.
More of a discussion (maybe we need a discussions tab). My understanding was that the API was to be used as a standalone API service rather then just a functions wrapper.
In most of our SaaS products the API is a seperate service. Currently
apps/api
there is no way to easily verify a user.The solution I went with
In the interest of keeping strong seperation of concern I have setup the API as follows.
https://api.mydomain.com
Middleware
In the middleware for the API I do a few things.
Server Component/Page Fetching
How do we attach the user to the api?
Clerk has a few different methods but the easiest way I found was just to include the Auth header. I created a fetch function similar to this that I can call from any server component in app or web or any other microservice we create.
This attached the session to the auth header as Clerk expects it.
Server Actions/Forms
The intent for Server Actions becomes really obvious in this now. We can use server actions to invoke the API. Here is a very simple one I use for submitting a form.
in apps/app/app/actions/nameAction.ts
Clerk In API
After this we can create the user session on the server.
If you use the JWT public key it will verify the session without sending a request to Clerks backend. I have created a function like so.
Then I just call this first in any API route that needs a user attached
Results
This makes the API a completely stand alone system that can still use the Clerk user for auth.
With how Next.js works it adds a strong seperation of concern. It also makes it extendable or replaceable. They push heavy that data fetching/mutations should happen server side. This has the benefit's of both and prevents any valuable info being leaked to the client.
Shortcomings
I am aware that calling a server action in itself is an api request. In early testing in other projects the impact with not really a concern. The benefits out weighed the negatives.
While this is definitely more code, I think its one of the core reasons why this repo is so popular. Everything is seperate, it makes it very easy to find things.
This extends on that fact. It felt a bit messy putting API routes in the app when we had a dedicated API service.
Describe alternatives you've considered
I would love some feedback on this. Maybe it can be improved.
Beta Was this translation helpful? Give feedback.
All reactions