{"id":19411776,"url":"https://github.com/nickheap2/sam-local-express","last_synced_at":"2025-04-24T10:34:06.952Z","repository":{"id":43081137,"uuid":"468530038","full_name":"NickHeap2/sam-local-express","owner":"NickHeap2","description":"Local testing of simple AWS SAM templates via Express","archived":false,"fork":false,"pushed_at":"2023-02-04T08:11:10.000Z","size":857,"stargazers_count":4,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-04-24T16:59:01.231Z","etag":null,"topics":["aws-sam","express","local"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NickHeap2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2022-03-10T22:28:32.000Z","updated_at":"2022-08-04T06:11:34.000Z","dependencies_parsed_at":"2023-02-08T04:17:17.092Z","dependency_job_id":null,"html_url":"https://github.com/NickHeap2/sam-local-express","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickHeap2%2Fsam-local-express","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickHeap2%2Fsam-local-express/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickHeap2%2Fsam-local-express/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NickHeap2%2Fsam-local-express/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NickHeap2","download_url":"https://codeload.github.com/NickHeap2/sam-local-express/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223950680,"owners_count":17230520,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-sam","express","local"],"created_at":"2024-11-10T12:23:04.928Z","updated_at":"2024-11-10T12:23:05.391Z","avatar_url":"https://github.com/NickHeap2.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sam-local-express\nLocal testing of simple AWS SAM templates via Express.  \nThe aim of this package is to support local testing of simple API gateways with attached AWS lambda functions/authorizers defined in an AWS SAM template.  \nSAM start-api should still be used to more accurately verify functionality before deployment.  \n\n[![Node.js CI](https://github.com/NickHeap2/sam-local-express/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/NickHeap2/sam-local-express/actions/workflows/main.yml)\n[![Standardjs](https://github.com/NickHeap2/sam-local-express/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/NickHeap2/sam-local-express/actions/workflows/lint.yml)\n[![CodeQL](https://github.com/NickHeap2/sam-local-express/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/NickHeap2/sam-local-express/actions/workflows/codeql-analysis.yml)\n\n## Supported functionality\n* The values `172.17.0.1` and `host.docker.internal` are replaced in your template with `localhost`\n* The functions `Equals`, `If`, `Not` and `FindInMap` are processed for conditions and environmental variables (`And` and `Or` are not yet)\n* Conditions are evaluated from parameters and mappings\n* Global environmental variables are populated from parameters, mappings and conditions\n* Http and Rest Apis are discovered and served under a single or multiple Express instances\n* Cors rules are applied from global and api level settings\n* Any authorizer lambda function defined in the template is called before the routes\n* Serverless functions with Path, Method and ApiId are attached to the Express instances\n* Routes are built like `http://localhost:3000/{api stage}/{function path}`\n* Any set cookies in the response have `Secure` removed so that they will work with http\n\n## Functionality that will be supported in the future\n* `And` and `Or` function processing\n* Environmental variables defined at the function level\n\n## Main packages used\n`cors` - cors configuration.  \n`express` - routing to the handlers.  \n`lambda-local` - invoking the lambda functions.  \n`nodemon` - watching code for any changes and restarting the server.  \n`yaml-cfn` - parsing the template file.\n\nSee below for an example of the type of template that this is designed to support\n\n## Installing\n### Globally\n``` bash\nnpm install --global sam-local-express\n```\nThen you can use it like this\n``` bash\nsam-local-express --template template.yaml\n```\n\n### Project level\n``` bash\nnpm install --save-dev sam-local-express\n```\n\nThen you can use it like this in your package.json\n``` json\n  \"scripts\": {\n    \"sam-local-express\": \"sam-local-express --template template.yaml\"\n  }\n```\nOr from the terminal like this\n``` bash\nnpx sam-local-express --template template.yaml\n```\n\nDetails of how to debug your serverless functions is found under `Debug APIs defined in a SAM template with Express all on port 4000`.\n\n## Usage\n\nUse --help to get a list of options\n``` text\nUsage: sam-local-express [options]\n\nOptions:\n  -V, --version                  output the version number\n  -t, --template \u003ctemplate\u003e      Source AWS SAM template yaml filename\n  -e, --extensions [extensions]  Comma separated list of file extensions to watch (default: \"js,json,yaml\")\n  -s, --singleport               If set then all APIs will be served on a single port, use stages to separate (default: false)\n  -b, --baseport [portnumber]    The base port for Express servers (default: 3000)\n  -a, --noauth                   Don't attach authorisers (default: false)\n  -h, --help                     display help for command\n```\n\n### Serve multiple APIs defined in a SAM template with Express servers starting at port 3000\n``` bash\nsam-local-express --template template.yaml\n```\n![multiple](https://github.com/NickHeap2/sam-local-express/blob/3f84f853a694f8eb6551c664f6f122a25ca35a1c/images/multiple.png)\n\n### Serve multiple APIs defined in a SAM template with Express all on port 4000\n``` bash\nsam-local-express --template template.yaml --singleport --baseport 4000\n```\n![single](https://github.com/NickHeap2/sam-local-express/blob/123c930c7725d2927f52fde5ba69708857b65fe4/images/single.png)\n\n### Serve multiple APIs defined in a SAM template with Express all on port 4000 and don't attach any auth function\n``` bash\nsam-local-express --template template.yaml --singleport --baseport 4000 --noauth\n```\n\n### Debug APIs defined in a SAM template with Express all on port 4000\n\nThere are two ways to debug your APIs:\n\nThe best way is to use the 'Run \"npm start\" in a debug terminal' option in VS Code by adding through the Add configuration Debug UI or setting your .vscode/launch.json to the below\n``` json\n{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"command\": \"npm run sam-local-express\",\n      \"name\": \"sam-local-express\",\n      \"request\": \"launch\",\n      \"type\": \"node-terminal\"\n    }\n  ]\n}\n```\n\nThe other way is to use --inspect-brk on the command line\n``` bash\nsam-local-express --inspect-brk --template template.yaml --singleport --baseport 4000\n```\nor in your package.json\n``` json\n    \"sam-local-express-debug\": \"node --inspect-brk ./node_modules/sam-local-express --template template.yaml\"\n```\nThen attach to the session via Attach in VS Code. The problem with this is that you have to hit continue in two files of the package before continuing to your own code.\n\n### Watching for changes\n\nYou can use standard nodemon config in your package.json to change how the file watching works.\n``` json\n  \"nodemonConfig\": {\n    \"ignore\": [\"test/*\", \"docs/*\"],\n    \"delay\": 2500\n  }\n```\n\n## Example template\n``` yaml\n---\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: \"AWS::Serverless-2016-10-31\"\nDescription: \u003e\n  test-apis\n\n# Parameters are populated atm with the default values\nParameters:\n  Environment:\n    Description: Environment to run under\n    Type: String\n    Default: \"local\"\n    AllowedValues:\n      - \"local\"\n      - \"qa\"\n      - \"prod\"\n  LocalEnvironmentType:\n    Description: Type of Local Environment to run under\n    Type: String\n    Default: \"one\"\n    AllowedValues:\n      - \"one\"\n      - \"two\"\n  PassedInParameter:\n    Description: An example parameter passed into the template\n    Type: String\n    Default: \"\"\n\n# Environment mapped variables\nMappings:\n  LocalEnvironmentSettings:\n    one:\n      DependencyUrl: 'http://172.17.0.1:3001/local/one/dependency'\n    two:\n      DependencyUrl: 'http://host.docker.internal:3001/local/two/dependency'\n  Environments:\n    local:\n      DependencyUrl: 'http://host.docker.internal:3001/local/dependency'\n    qa:\n      DependencyUrl: 'http://172.17.0.1:3001/qa/dependency'\n    prod:\n      DependencyUrl: 'http://my.server.com:3001/prod/dependency'\n\n# apply some conditions\nConditions:\n  LocalEnvironment: !Equals [ !Ref Environment, 'local' ]\n  NotLocalEnvironment: !Not [ !Equals [ !Ref Environment, 'local' ] ]\n\n# Global function env vars\nGlobals:\n  Api:\n    Cors:\n      AllowMethods: 'OPTIONS'\n  Function:\n    Environment:\n      Variables:\n        DEPENDENCY_URL: !FindInMap [Environments, !Ref Environment, DependencyUrl]\n        # the if will get resolved\n        DEPENDENCY_URL_IF: !If [\n          LocalEnvironment,\n          !FindInMap [LocalEnvironmentSettings, !Ref LocalEnvironmentType, DependencyUrl],\n          !FindInMap [Environments, !Ref Environment, DependencyUrl]\n        ]\n        MY_PARAMETER_VARIABLE:\n          Ref: PassedInParameter\n        MY_SECRET:\n          Fn::Sub: \"{{resolve:secretsmanager:${SecretType}:SecretString:secretValue}}\"\n    Runtime: nodejs14.x\n    Timeout: 15\n    Layers:\n      - !Ref BaseLayer\n\n# Http API Gateway and Resources\nResources:\n  # http api with routes defined on function events,\n  # events are routed through the auth function first\n  TestHttpApiv1:\n    Type: AWS::Serverless::HttpApi\n    Properties:\n      Auth:\n        DefaultAuthorizer: LambdaRequestAuthorizer\n        Authorizers:\n          LambdaRequestAuthorizer:\n            FunctionPayloadType: REQUEST\n            FunctionArn: !GetAtt lambdaAuthorizer.Arn\n      CorsConfiguration: true\n      DefinitionBody:\n        'Fn::Transform':\n          Name: AWS::Include\n          Parameters:\n            Location: openapi.yaml\n      StageName: v1\n\n  # http api with routes defined on function events\n  TestHttpApiv2:\n    Type: AWS::Serverless::HttpApi\n    Properties:\n      Auth:\n        DefaultAuthorizer: SimpleLambdaRequestAuthorizer\n        Authorizers:\n          SimpleLambdaRequestAuthorizer:\n            FunctionArn: !GetAtt simpleLambdaAuthorizer.Arn\n            EnableSimpleResponses: true\n            Identity:\n              Headers:\n                - Authorization\n      CorsConfiguration: 'localhost'\n      DefinitionBody:\n        'Fn::Transform':\n          Name: AWS::Include\n          Parameters:\n            Location: openapi.yaml\n      StageName: v2\n\n  # http api with token authorizer\n  TestHttpApiv3:\n    Type: AWS::Serverless::HttpApi\n    Properties:\n      Auth:\n        DefaultAuthorizer: SimpleLambdaRequestAuthorizer\n        Authorizers:\n          SimpleLambdaRequestAuthorizer:\n            FunctionArn: !GetAtt simpleLambdaAuthorizer.Arn\n            EnableSimpleResponses: true\n      CorsConfiguration: true\n      DefinitionBody:\n        'Fn::Transform':\n          Name: AWS::Include\n          Parameters:\n            Location: openapi.yaml\n      StageName: v3\n\n  # http api with external authorizer that will be ignored\n  TestHttpApiv4:\n    Type: AWS::Serverless::HttpApi\n    Properties:\n      Auth:\n        DefaultAuthorizer: ExternalAuthorizer\n        Authorizers:\n          ExternalAuthorizer:\n            FunctionArn:\n              Fn::ImportValue: ExternalAuthorizer\n            EnableSimpleResponses: true\n      CorsConfiguration: true\n      DefinitionBody:\n        'Fn::Transform':\n          Name: AWS::Include\n          Parameters:\n            Location: openapi.yaml\n      StageName: v4\n\n  # http api with no auth and cors\n  TestHttpApiv5:\n    Type: AWS::Serverless::HttpApi\n    Properties:\n      CorsConfiguration:\n        AllowCredentials: true\n        AllowHeaders: 'Authorization, *'\n        AllowMethods: 'GET, POST, DELETE, *'\n        AllowOrigins: 'http://localhost, https://stackoverflow.com'\n        ExposeHeaders: 'Date, x-api-id'\n        MaxAge: 100\n      DefinitionBody:\n        'Fn::Transform':\n          Name: AWS::Include\n          Parameters:\n            Location: openapi.yaml\n      StageName: v5\n\n  # base layer for functions, this isn't used atm the handlers run against your dev dependencies instead\n  BaseLayer:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: base_layer\n      Description: Base layer for containers\n      ContentUri: base\n      CompatibleRuntimes:\n        - nodejs14.x\n      RetentionPolicy: Delete\n    Metadata:\n      BuildMethod: nodejs14.x\n\n  # a secret from secret manager, we don't do anything with this yet\n  EncryptionSecret:\n    Type: AWS::SecretsManager::Secret\n    Properties:\n      Name: my-secret\n      Description: My Secret\n      GenerateSecretString:\n        SecretStringTemplate: \"{}\"\n        GenerateStringKey: \"secretValue\"\n        ExcludeCharacters: '\"@/\\\\'\n\n  # functions\n  pathParamTestGet:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.pathParamTestGet\n      Events:\n        Apiv1:\n          Type: HttpApi\n          Properties:\n            Path: /{pathParam}/test/\n            Method: get\n            ApiId: !Ref TestHttpApiv1\n\n  doublePathParamTestGet:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.doublePathParamTestGet\n      Events:\n        Apiv1:\n          Type: HttpApi\n          Properties:\n            Path: /{pathParam1}/test/{pathParam2}/testagain\n            Method: get\n            ApiId: !Ref TestHttpApiv1\n\n  testGet:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.testGet\n      Events:\n        Apiv1Get:\n          Type: HttpApi\n          Properties:\n            Path: /test\n            Method: get\n            ApiId: !Ref TestHttpApiv1\n        Apiv1Delete:\n          Type: HttpApi\n          Properties:\n            Path: /test\n            Method: delete\n            ApiId: !Ref TestHttpApiv1\n        Apiv2:\n          Type: HttpApi\n          Properties:\n            Path: /test\n            Method: get\n            ApiId: !Ref TestHttpApiv2\n        Apiv3:\n          Type: HttpApi\n          Properties:\n            Path: /test\n            Method: get\n            ApiId: !Ref TestHttpApiv3\n        Apiv5:\n          Type: HttpApi\n          Properties:\n            Path: /test\n            Method: get\n            ApiId: !Ref TestHttpApiv5\n\n  proxyTestGet:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.proxyTestGet\n      Events:\n        Apiv1:\n          Type: HttpApi\n          Properties:\n            Path: /proxy/{proxy}+\n            Method: get\n            ApiId: !Ref TestHttpApiv1\n\n  testPost:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.testPost\n      Events:\n        Apiv2:\n          Type: HttpApi\n          Properties:\n            Path: /test\n            Method: post\n            ApiId: !Ref TestHttpApiv2\n\n  scheduledPost:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.scheduledPost\n      Events:\n        Apiv2:\n          Type: HttpApi\n          Properties:\n            Path: /scheduled\n            Method: post\n            ApiId: !Ref TestHttpApiv2\n        EveryTenMinutes:\n          Type: Schedule\n          Properties:\n            Schedule: 'rate(10 minutes)'\n            Name: EveryTenMinutes\n            Description: Run every 10 minutes\n            Enabled: true\n\n  # an authorizer function\n  lambdaAuthorizer:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.authorizer\n\n  simpleLambdaAuthorizer:\n    Type: AWS::Serverless::Function\n    Properties:\n      Handler: handlers/v1/index.simpleAuthorizer\n\nOutputs:\n  TestHttpApiv1:\n    Description: \"API Gateway endpoint URL for test api v1\"\n    Value:\n      Fn::Sub: https://${TestHttpApiv1}.execute-api.${AWS::Region}.amazonaws.com/v1\n  TestHttpApiv2:\n    Description: \"API Gateway endpoint URL for test api v2\"\n    Value:\n      Fn::Sub: https://${TestHttpApiv2}.execute-api.${AWS::Region}.amazonaws.com/v2\n  TestHttpApiv3:\n    Description: \"API Gateway endpoint URL for test api v3\"\n    Value:\n      Fn::Sub: https://${TestHttpApiv3}.execute-api.${AWS::Region}.amazonaws.com/v3\n  TestHttpApiv4:\n    Description: \"API Gateway endpoint URL for test api v4\"\n    Value:\n      Fn::Sub: https://${TestHttpApiv4}.execute-api.${AWS::Region}.amazonaws.com/v4\n  TestHttpApiv5:\n    Description: \"API Gateway endpoint URL for test api v5\"\n    Value:\n      Fn::Sub: https://${TestHttpApiv5}.execute-api.${AWS::Region}.amazonaws.com/v5\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnickheap2%2Fsam-local-express","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnickheap2%2Fsam-local-express","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnickheap2%2Fsam-local-express/lists"}