Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(types): support generics#284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
naorpeled wants to merge18 commits intomain
base:main
Choose a base branch
Loading
fromfeat/types/support-alb

Conversation

@naorpeled
Copy link
Collaborator

@naorpelednaorpeled commentedFeb 6, 2025
edited
Loading

Basic Type-Safe Setup

import{API,Request,Response,ALBContext,APIGatewayContext,APIGatewayV2Context}from'lambda-api';import{ALBEvent,APIGatewayProxyEvent,APIGatewayProxyEventV2}from'aws-lambda';// Initialize with type inferenceconstapi=newAPI();

Type-Safe Request Handlers

ALB Handler Example

interfaceUserData{id:string;name:string;email:string;}// Type-safe ALB request handlerapi.get<UserData,ALBContext>('/users',(req,res)=>{// req.requestContext is typed as ALBContextconsole.log(req.requestContext.elb.targetGroupArn);// Type-safe responseres.json({id:'123',name:'John Doe',email:'john@example.com'});});

API Gateway v1 Handler Example

// Type-safe API Gateway v1 request handlerapi.post<UserData,APIGatewayContext>('/users',(req,res)=>{// req.requestContext is typed as APIGatewayContextconsole.log(req.requestContext.requestId);console.log(req.requestContext.identity.sourceIp);res.json({id:req.requestContext.requestId,name:req.body.name,email:req.body.email});});

API Gateway v2 Handler Example

// Type-safe API Gateway v2 request handlerapi.put<UserData,APIGatewayV2Context>('/users/:id',(req,res)=>{// req.requestContext is typed as APIGatewayV2Contextconsole.log(req.requestContext.http.sourceIp);res.json({id:req.params.id,name:req.body.name,email:req.body.email});});

Type-Safe Middleware

Source-Agnostic Middleware

import{Middleware,isApiGatewayContext,isApiGatewayV2Context,isAlbContext}from'lambda-api';constsourceAgnosticMiddleware:Middleware=(req,res,next)=>{// Type guards help narrow down the request context typeif(isAlbContext(req.requestContext)){// ALB specific logicconsole.log(req.requestContext.elb.targetGroupArn);}elseif(isApiGatewayV2Context(req.requestContext)){// API Gateway v2 specific logicconsole.log(req.requestContext.http.sourceIp);}elseif(isApiGatewayContext(req.requestContext)){// API Gateway v1 specific logicconsole.log(req.requestContext.identity.sourceIp);}next();};api.use(sourceAgnosticMiddleware);

Source-Specific Middleware

// ALB-specific middlewareconstalbMiddleware:Middleware<any,ALBContext>=(req,res,next)=>{// req.requestContext is typed as ALBContextconsole.log(req.requestContext.elb.targetGroupArn);next();};// API Gateway v2 specific middlewareconstapiGwV2Middleware:Middleware<any,APIGatewayV2Context>=(req,res,next)=>{// req.requestContext is typed as APIGatewayV2Contextconsole.log(req.requestContext.http.sourceIp);next();};

Type-Safe Error Handling

import{ErrorHandlingMiddleware}from'lambda-api';consterrorHandler:ErrorHandlingMiddleware=(error,req,res,next)=>{if(isAlbContext(req.requestContext)){// ALB specific error handlingres.status(500).json({message:error.message,targetGroup:req.requestContext.elb.targetGroupArn});}else{// Default error handlingres.status(500).json({message:error.message});}};api.use(errorHandler);

Advanced Type-Safe Examples

Custom Request Types

interfaceCustomQuery{filter?:string;page?:string;}interfaceCustomParams{userId:string;}interfaceCustomBody{name:string;email:string;}// Fully typed request handlerapi.get<UserData,ALBContext,CustomQuery,CustomParams,CustomBody>('/users/:userId',(req,res)=>{// All properties are properly typedconst{ filter, page}=req.query;const{ userId}=req.params;const{ name, email}=req.body;res.json({id:userId,    name,    email});});

Response Type Extensions

// Extend Response interface with custom methodsdeclare module'lambda-api'{interfaceResponse{sendWithTimestamp?:(data:any)=>void;}}constresponseEnhancer:Middleware=(req,res,next)=>{res.sendWithTimestamp=(data:any)=>{res.json({      ...data,timestamp:Date.now()});};next();};api.use(responseEnhancer);// Use custom response methodapi.get('/users',(req,res)=>{res.sendWithTimestamp({name:'John'});});

Using Built-in Auth Property

interfaceAuthInfo{userId:string;roles:string[];type:'Bearer'|'Basic'|'OAuth'|'Digest'|'none';value:string|null;}functionhasAuth(req:Request):req isRequest&{auth:AuthInfo}{return'auth'inreq&&req.auth?.type!==undefined;}api.get('/protected',(req,res)=>{if(hasAuth(req)){// req.auth is now typed as AuthInfoconst{ userId, roles}=req.auth;res.json({ userId, roles});}else{res.status(401).json({message:'Unauthorized'});}});

Running the API

// Type-safe run methodexportconsthandler=async(event:ALBEvent|APIGatewayProxyEvent|APIGatewayProxyEventV2,context:any)=>{returnapi.run(event,context);};

Type Guards Usage

import{isAlbContext,isAlbEvent,isAlbRequest,isApiGatewayContext,isApiGatewayEvent,isApiGatewayRequest,isApiGatewayV2Context,isApiGatewayV2Event,isApiGatewayV2Request}from'lambda-api';api.use((req,res,next)=>{// Event type guardsif(isAlbEvent(req.app._event)){// ALB specific logic}// Context type guardsif(isAlbContext(req.requestContext)){// ALB specific logic}// Request type guardsif(isAlbRequest(req)){// ALB specific logic}next();});

Best Practices

  1. Always specify response types for better type inference:
interfaceResponseType{message:string;code:number;}api.get<ResponseType>('/status',(req,res)=>{res.json({message:'OK',code:200});});
  1. Use type guards for source-specific logic:
api.use((req,res,next)=>{if(isAlbContext(req.requestContext)){// ALB-specific loggingconsole.log(`ALB Request to${req.requestContext.elb.targetGroupArn}`);}next();});
  1. Leverage TypeScript's type inference with middleware:
consttypedMiddleware:Middleware<ResponseType,ALBContext>=(req,res,next)=>{// Full type information availablenext();};
  1. Use source-specific error handling:
api.use((error,req,res,next)=>{constbaseError={message:error.message,timestamp:newDate().toISOString()};if(isAlbContext(req.requestContext)){res.status(500).json({      ...baseError,targetGroup:req.requestContext.elb.targetGroupArn});}elseif(isApiGatewayV2Context(req.requestContext)){res.status(500).json({      ...baseError,stage:req.requestContext.stage});}else{res.status(500).json(baseError);}});

Issues

implements#276
andcloses#244

@vandrade-git
Copy link
Contributor

This seems like a great change.

I've just tried on a project I have and noticed a few things. Given:

api.get<Response, ALBContext>('/health', async (req, res) => {  console.log(req.requestContext.elb); <-- also type any, any, any  res.json({ status: 'ok' });});

bothreq andres have an implicit type any so the type checking does not seem to be doing what is described in the README.

src/index.ts:20:52 - error TS7006: Parameter 'req' implicitly has an 'any' type.20 api.get<Response, ALBContext>('/health', async (req, res) => {src/index.ts:20:57 - error TS7006: Parameter 'res' implicitly has an 'any' type.20 api.get<Response, ALBContext>('/health', async (req, res) => {

This seems to be a limitation of the type

...(  | Middleware<TResponse, TContext, TQuery, TParams, TBody>  | HandlerFunction<TResponse, TContext, TQuery, TParams, TBody>)...

Something like:

const health: HandlerFunction<object, ALBContext> = async (req, res) => {  console.log(req.requestContext.elb);  res.json({ status: 'ok' });};// public health endpointapi.get<Response, ALBContext>('/health', health);

seems to over fine but it is a bit more cumbersome.

@naorpeled
Copy link
CollaboratorAuthor

naorpeled commentedFeb 9, 2025
edited
Loading

This seems like a great change.

I've just tried on a project I have and noticed a few things. Given:

api.get<Response, ALBContext>('/health', async (req, res) => {  console.log(req.requestContext.elb); <-- also type any, any, any  res.json({ status: 'ok' });});

bothreq andres have an implicit type any so the type checking does not seem to be doing what is described in the README.

src/index.ts:20:52 - error TS7006: Parameter 'req' implicitly has an 'any' type.20 api.get<Response, ALBContext>('/health', async (req, res) => {src/index.ts:20:57 - error TS7006: Parameter 'res' implicitly has an 'any' type.20 api.get<Response, ALBContext>('/health', async (req, res) => {

This seems to be a limitation of the type

...(  | Middleware<TResponse, TContext, TQuery, TParams, TBody>  | HandlerFunction<TResponse, TContext, TQuery, TParams, TBody>)...

Something like:

const health: HandlerFunction<object, ALBContext> = async (req, res) => {  console.log(req.requestContext.elb);  res.json({ status: 'ok' });};// public health endpointapi.get<Response, ALBContext>('/health', health);

seems to over fine but it is a bit more cumbersome.

I see, I'll dig into it in the next few days.
Thanks for the feedback, very appreciated!

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

[Feature request] Typescript - Support generic request / response types

3 participants

@naorpeled@vandrade-git

[8]ページ先頭

©2009-2025 Movatter.jp