๐บ๏ธ Server Context #7644
Replies: 16 comments 39 replies
-
There was a proposal a while ago that I liked which worked similarly to react context. I like this for its inherent type safety: // database-context.ts
export const databaseContext = createServerContext<Database>()
export async function setDatabaseContext({ request, context, next }: MiddlewareArgs) {
let userId = myGetUserIdFromRequest(request)
let db = myCreateDbConnection(userId)
databaseContext.set(context, db)
return next()
}
// timing-headers.ts
import { databaseContext } from './database-context.ts'
export async function queryTimingHeaders({ request, context, next }: MiddlewareArgs) {
const db = databaseContext.get(context) // throws if not set, maybe add a `getOptional` function for the alternate case?
myCaptureTimingsFor(request, db);
let response = await next();
let timings = await getTimingsFromDb(request);
response.headers.append("Server-Timings", timings);
}
// root.tsx
export const middleware = [setDatabaseContext, queryTimingHeaders] |
Beta Was this translation helpful? Give feedback.
-
Nice, how would the context type works? Especially when multiple routes would define some context, will we have to merge the types ourselves? |
Beta Was this translation helpful? Give feedback.
-
Do I get scaling problems when I use serverside context ? |
Beta Was this translation helpful? Give feedback.
-
One of the things my team has been using |
Beta Was this translation helpful? Give feedback.
-
From what I understand, Server Context is used to define values that other parts of the request flow can access and is needed because middlewares can't do that? Wouldn't it be better to merge this with middlewares? So you can define this I think that's how middlewares typically work in other frameworks like Express or Hono, e.g. being similar to Hono: export function middleware({ request, params, context, next }) {
let userId = myGetUserIdFromRequest(request)
ctx.set("db", myCreateDbConnection(userId))
await next()
}
export function loader({ context }) {
let db = context.get("db")
// use db here
} This also enables a pattern I like in Hono where you can use a Symbol as context key and then hide (and type) accessing the context behind a function // app/services/db.server.ts
const DB = Symbol()
export function middleware({ request, params, context, next }: MiddlewareFunctionArgs) {
let userId = myGetUserIdFromRequest(request)
ctx.set(DB, myCreateDbConnection(userId))
await next()
}
export function getDB(context: ServerContext) {
let db = ctx.get(DB)
if (db instanceof Database) return db
throw new Error("Missing DB in context")
}
// in some route
export function loader({ context }: LoaderFunctionArgs) {
let db = getDB(context)
// use db here which is typed as Database instance
} |
Beta Was this translation helpful? Give feedback.
-
I know Shopify have a code freeze in the works but I was wondering if there was an eta on this kind of functionality. At present we're building out on the Remix App Template and AWS with a DynamoDB Session provider. As part the server loaders we perform API request signing via a Cognito lookup to hit our private APIs. Because there is no concept of server context we can use a bit of caching to store credentials per session but having a shared context would be incredibly useful rather than looking up/caching per loader. We had a look at Unless there's something else in our understanding Remix we're missing at present, the paralellization of loader execution means currently it's not possible to share session/context across loaders and rather look them up each time? |
Beta Was this translation helpful? Give feedback.
-
Thinking about the usage, instead of this export const context = {
db: getMyDataStorageContext,
payments: getMyPaymentsContext,
}; Would it be possible to do without keys? export const context = [getMyDataStorageContext, getMyPaymentsContext] They can still run in parallel (like if you did Promise.all), I'm thinking for a library that wants to inject something into context, e.g. for Remix Auth, with the current usage proposed I would need to ask the user always for the same key, e.g. to make the session available always use: export const context = { session: authenticator.session } If they used another key any middleware depending on that code will not work anymore. Instead if it was an array that received a mutable context object, I could do something like this: const symbol = Symbol()
async function session(request, context) {
let session = await sessionStorage.getSession(request.headers.get("cookie"))
context.set(symbol, session)
}
export const context = [session]
function getSession(context) {
return context.get(symbol) as Session | null
}
function requireUser(request, context) {
let session = getSession(context)
if (!session) throw redirect("/login")
}
export const middleware = [requireUser] By doing this I can ensure my middleware has access to the correct Symbol and it can retrieve the context value I need as long as the context is defined, I think the let context = new Map<Symbol, unknown>() |
Beta Was this translation helpful? Give feedback.
-
would it be too crazy to allow the user, on a per route fashion, how to run the context + middleware + loader/actions? I'm just thinking of something like export const run = [middleware, context, action, loader] in which the first element runs first, the second, second, so on allowing a really powerful pattern in my opinion where by simply moving the elements of the array from one place to the other, you are setting the rendering you want |
Beta Was this translation helpful? Give feedback.
-
I think a major improvement this brings is that is not easy to include typescript into |
Beta Was this translation helpful? Give feedback.
-
It would be really great to have this RFC implemented in Remix. The lack of middleware/server-context/route-context in Remix makes it so hard to handle cross cutting concerns (like auth) in a single place. The existing |
Beta Was this translation helpful? Give feedback.
-
Having loaders run in parallel for speed is a neat idea but there are instances where a parent loader needs to pass data to a child loader. IMO, there should be a setting to enable a specific parent route to load a child route sequentially and allow the transfer of loader data to get the best of both worlds. It should be up to the developer of the application to choose if a route loads in parallel or sequentially. |
Beta Was this translation helpful? Give feedback.
-
An example use case for this feature is how Shopify themselves are using it in the Hydrogen template. https://github.com/Shopify/hydrogen/blob/main/templates/hello-world/server.ts#L60 They are creating an API client in the custom server, and then attaching it to context using I would love to be able to do something like this in my own Remix apps, without needing to define a custom server, when all I need is a way to set context. |
Beta Was this translation helpful? Give feedback.
-
Iโm confused why a lot of people here think this is "urgently and importantly needed". We kinda have it already ? In the sense that we have an empty context object (empty if we haven't filled it with a load context) that is shared between loaders. context.server.tsx : export function getSession(request: Request, context: any): Promise<Session> {
if(!context.session)
context.session = sessionProvider.getSession(request.headers.get("cookie"));
return context.session;
} Any route loader : import {getSession} from "./context.server";
export async function loader({request, context}: LoaderFunctionArg) {
const session = await getSession(request, context);
// ...
} Sure, formalizing it would be a win, but this work-around seems sufficient in most cases ? What am I missing ? |
Beta Was this translation helpful? Give feedback.
-
Maybe I am wrong but I am seeing two access patterns inside actions/loaders for the server context: const action = () => {
const { db } = getContext();
}
// and
const action = ({ context }) => {
const { db } = context;
} Are both valid or only one of these? I'm interested because the second access pattern would make testing easier (just set a property on an object before passing it to the |
Beta Was this translation helpful? Give feedback.
-
Have any progress? I would like to use serverContext. For us, we need a way to pass some vars by loader.context |
Beta Was this translation helpful? Give feedback.
-
Closing in favor of remix-run/react-router#11565 |
Beta Was this translation helpful? Give feedback.
-
Server context allows you to create a value in one place and share it across the rest of the route hierarchy below the context: data, abstractions, dependency injection, etc.
It should replace the need for
getLoadContext
in adapters making it possible to share context abstractions between apps using different server adapters. It also enables context at the route level instead of the app level.Proposal
Future Flag
This feature depends on the single fetch and middleware flags, and will likely just be implemented along with middleware as the same flag.
Signature
Usage in Routes
Server Context is defined as a route module export. The defining route and all routes below the context will have access to the contexts created, accessible by the key used in the route definition. Multiple contexts can be created in a single context route.
Context can be accessed in various places in your app:
Example
Some databases allow you to create a connection that is scoped to an individual user. This allows you to create a connection to the database once per request and share it across the entire app.
Context Definition
Route Configuration
Middleware Access
Loader/Action Access
Beta Was this translation helpful? Give feedback.
All reactions