Nick
Nick9mo ago

Creating inner context for AWS Lambda Context Options

Hi All, I have been using tRPC for many routes, and I have started to realize I need to call certain routes on the server-side (lambda / serverless in my case), and so I was trying to follow the documentation on how to create an inner context / outer context so I can call my tRPC routes from other routes, but I am stuck and I am confused on how to go about doing it for AWS lambda context as the documentation example is rather simple. This is what I have so far:
import { inferAsyncReturnType } from "@trpc/server";
import type { CreateAWSLambdaContextOptions } from "@trpc/server/adapters/aws-lambda";
import type { APIGatewayProxyEventV2 } from "aws-lambda";
import { CognitoJwtVerifier } from "aws-jwt-verify";

/**
* Defines your inner context shape.
* Add fields here that the inner context brings.
*/
interface CreateInnerContextOptions extends Partial<CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>>{
context: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>["context"] | undefined;
event: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>["event"] | undefined;

}

/**
* Inner context. Will always be available in your procedures, in contrast to the outer context.
*
* Also useful for:
* - testing, so you don't have to mock Next.js' `req`/`res`
* - tRPC's `createServerSideHelpers` where we don't have `req`/`res`
*
* @see https://trpc.io/docs/context#inner-and-outer-context
*/
export async function createContextInner(opts?: CreateInnerContextOptions) {
return {
context: opts?.context,
event: opts?.event,
};
}

/**
* Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".
*
* @see https://trpc.io/docs/context#inner-and-outer-context
*/

export async function createContext({ event }: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) {
async function getUserFromHeader() {
// Verifier that expects valid access tokens:
if (process.env.USER_POOL_ID === undefined || process.env.USER_POOL_CLIENT_ID == undefined) {
throw new Error("Missing environment variables");
}
if (event.headers.authorization) {
const token = event.headers.authorization.split(" ")[1];
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.USER_POOL_ID,
tokenUse: "id",
clientId: process.env.USER_POOL_CLIENT_ID,
});
// the Authorization header will contain the JWT token as a Bearer token, we need to remove the Bearer part before we can verify the token
const payload = await verifier.verify(token);
return {
role: payload["cognito:groups"],
username: payload["cognito:username"],
orgId: payload["custom:organizationId"] ? (payload["custom:organizationId"] as string) : undefined,
};
}
throw new Error("No authorization header");
}
const user = await getUserFromHeader();

const contextInner = await createContextInner({ context: undefined, event: undefined });

return {
...contextInner,
event: event,
apiVersion: (event as { version?: string }).version ?? "1.0",
user: user,
};
}



export type Context = inferAsyncReturnType<typeof createContextInner>;
import { inferAsyncReturnType } from "@trpc/server";
import type { CreateAWSLambdaContextOptions } from "@trpc/server/adapters/aws-lambda";
import type { APIGatewayProxyEventV2 } from "aws-lambda";
import { CognitoJwtVerifier } from "aws-jwt-verify";

/**
* Defines your inner context shape.
* Add fields here that the inner context brings.
*/
interface CreateInnerContextOptions extends Partial<CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>>{
context: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>["context"] | undefined;
event: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>["event"] | undefined;

}

/**
* Inner context. Will always be available in your procedures, in contrast to the outer context.
*
* Also useful for:
* - testing, so you don't have to mock Next.js' `req`/`res`
* - tRPC's `createServerSideHelpers` where we don't have `req`/`res`
*
* @see https://trpc.io/docs/context#inner-and-outer-context
*/
export async function createContextInner(opts?: CreateInnerContextOptions) {
return {
context: opts?.context,
event: opts?.event,
};
}

/**
* Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".
*
* @see https://trpc.io/docs/context#inner-and-outer-context
*/

export async function createContext({ event }: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2>) {
async function getUserFromHeader() {
// Verifier that expects valid access tokens:
if (process.env.USER_POOL_ID === undefined || process.env.USER_POOL_CLIENT_ID == undefined) {
throw new Error("Missing environment variables");
}
if (event.headers.authorization) {
const token = event.headers.authorization.split(" ")[1];
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.USER_POOL_ID,
tokenUse: "id",
clientId: process.env.USER_POOL_CLIENT_ID,
});
// the Authorization header will contain the JWT token as a Bearer token, we need to remove the Bearer part before we can verify the token
const payload = await verifier.verify(token);
return {
role: payload["cognito:groups"],
username: payload["cognito:username"],
orgId: payload["custom:organizationId"] ? (payload["custom:organizationId"] as string) : undefined,
};
}
throw new Error("No authorization header");
}
const user = await getUserFromHeader();

const contextInner = await createContextInner({ context: undefined, event: undefined });

return {
...contextInner,
event: event,
apiVersion: (event as { version?: string }).version ?? "1.0",
user: user,
};
}



export type Context = inferAsyncReturnType<typeof createContextInner>;
3 Replies
Nick
Nick9mo ago
It seems that I somehow need to tell all my exising code that a user will exist if called from the API, but not for internal calls, I am confused on how this is solved with the example online: https://trpc.io/docs/server/context#inner-and-outer-context All they do is:
return {
...contextInner,
req: opts.req,
res: opts.res,
};
return {
...contextInner,
req: opts.req,
res: opts.res,
};
And I did something similar, but since we are defining export type Context = inferAsyncReturnType<typeof createContextInner>;, I don't see how it ever knows about the req and res, and thus there are errors everywhere
ghard1314
ghard13149mo ago
If your goal is to tell if a user is present or not, you should do this through middleware. Here are the docs you want for this probabily https://trpc.io/docs/server/server-side-calls
Server Side Calls | tRPC
You may need to call your procedure(s) directly from the same server they're hosted in, router.createCaller() can be used to achieve this.
ghard1314
ghard13149mo ago
Also I dont think you should call one route from another. You should abstract out whatever logic you want to call into a separate function and call it directly from the server