{"id":17651023,"url":"https://github.com/g-mark/custom-nginx-vapor","last_synced_at":"2025-03-30T08:27:20.739Z","repository":{"id":91822467,"uuid":"244193206","full_name":"g-mark/custom-nginx-vapor","owner":"g-mark","description":"Example of putting NGiNx in front of Vapor on Heroku, with some custom setup","archived":false,"fork":false,"pushed_at":"2020-03-02T00:00:23.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-05T10:34:46.669Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/g-mark.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-01T17:30:34.000Z","updated_at":"2020-03-02T00:00:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"dd7a4e60-5f72-4887-8379-37c88e5b4450","html_url":"https://github.com/g-mark/custom-nginx-vapor","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/g-mark%2Fcustom-nginx-vapor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g-mark%2Fcustom-nginx-vapor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g-mark%2Fcustom-nginx-vapor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g-mark%2Fcustom-nginx-vapor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/g-mark","download_url":"https://codeload.github.com/g-mark/custom-nginx-vapor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246293801,"owners_count":20754324,"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":[],"created_at":"2024-10-23T11:40:00.397Z","updated_at":"2025-03-30T08:27:20.733Z","avatar_url":"https://github.com/g-mark.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# custom-nginx-vapor\nExample of putting NGiNx in front of Vapor on Heroku - kind of like [test-nginx-vapor](https://github.com/g-mark/test-nginx-vapor), but a little more advanced.  Note: this was built with Vapor 3.  Probably only the custom middleware would need to be updated for Vapor 4.  This repo is mostly about standing up NGiNx in front of Vapor on Heroku.\n\nThis setup only proxies specific routes to Vapor, and the rest are delivered as static files or a 404.\n\nThere are four Vapor routes:\n\n| path | content                      |\n| ---- | ---------------------------- |\n| /    | standard \"it works!\" message |\n| /hello/:string | Says \"hello, :string\" |\n| /hi | causes a 404 from within Vapor, delivering the same file as nginx\u003cbr\u003e(nginx has a `/hi` location, but the vapor app doesn't have a `/hi` route) |\n| /crash/:string | causes an internal server error from Vapor\u003cbr\u003e(nginx has a `/crash/*` location, and the vapor app tries to parse the extra path component as an int) |\n\n\nThere are two requests handled by NGiNx (well, in addition to all the \"assets\"):\n\n```\n/index.html\n/other.html\n```\n\n\n\n### HTTPS\n\nAll requests are forced to https, with this goodie in the `nginx.conf.erb`config file:\n\n```\n# Force http requests to be https\nif ($http_x_forwarded_proto != \"https\") {\n    return 301 https://$host$request_uri;\n}\n```\n\n\n\n### HTTP Errors\n\nAny 404 errors that happen deliver the `/Public/404.html` file.  404 errors can be caught by NGiNx _or_ Vapor. The NGiNx 404 error handling is specified in the `nginx.conf.erb`config file:\n\n```\n# custom error page\nerror_page 404 /404.html;\nlocation = /404.html {\n    internal;\n}\n```\n\n404 errors that happen inside of Vapor are handled using a custom middleware which delivers the same `/Public/404.html` file.  The default in Vapor is to return all errors in a JSON format.  If you're using Leaf as a templating engine, you can also set Leaf up to deliver a custom html error page.  I wrote a custom middleware so that I could use the _exact same file_ for 404s caught be either service.\n\nThis middleware is in `HTMLErrorMiddleware.swift`, and is pretty easy to configure, as it supports http status ranges:\n\n```swift\nvar middlewares = MiddlewareConfig()\n\nmiddlewares.use(\n    HTMLErrorMiddleware(\n        .public(file: \"404.html\", for: 404),\t\t // use /Public/404.html for all 404 errors\n        .resource(file: \"5xx.html\", for: 500...) // use /Resources/5xx.html for all \u003e= 500\n    )\n)\n\nservices.register(middlewares)\n```\n\nI kind of threw it together, so there's certainly room for improvement.  For example, if no file has been specified for a specific status code, the middleware will just send some plan text.\n\n\n\n## Try it live\n\nIt's on a free Heroku plan, so if no requests have been made in a little while, it might take a second to warm up:\n\nhttps://custom-nginx-vapor.herokuapp.com/\n\n\n\n## Setup\n\n#### Heroku setup\n\n1. Create an app\n2. Add my fancy custom buildpack, a fork of the Heroku official NGiNx buildpack:  \n   [heroku-buildpack-nginx-vapor](https://github.com/g-mark/heroku-buildpack-nginx-vapor)  \n   `https://github.com/g-mark/heroku-buildpack-nginx-vapor`  \n   Why a custom buildpack?  I wanted one that was built to work with Vapor out of the box.\n3. Add official Vapor buildpack - I used:  \n   [Heroku buildpack: swift](https://elements.heroku.com/buildpacks/vapor-community/heroku-buildpack)  \n   `vapor/vapor`\n4. Grab the app's git URL.  (in `Settings`, something like `https://git.heroku.com/YOUR-APP-NAME-HERE.git`)\n5. Fork this repo, clone it locally\n6. Add your app's git URL as an origin for you local repo\n7. Push the repo to the Heroku origin.\n   This will deploy the app.\n\n#### How it works\n\nWhen code is pushed to the app's git repo, Heroku will run a post_receive hook that triggers each of the buildpacks added to the app, in the order that they are listed in your `Settings` pane.  This also happens to be the same order that they were added in the above steps: `ngnix` then `vapor`.\n\nOnce both buildpacks have finished building, the `Procfile` is executed.\n\nThe `Procfile` in this repo has a single line, that runs two commands:\n```\nweb: bin/start-nginx Run serve --env production --port 8080 --hostname 0.0.0.0\n```\n\n- `bin/start-nginx` starts ngnix\n- `Run serve --env production --port 8080 --hostname 0.0.0.0` runs the vapor app.\n\nWhen ngnix is built, it uses the config file at `config/nginx.conf.erb` as the primary configuration for the ngnix server.  This contains the core routing needed to have NGiNx stand as a proxy in front of Vapor.  In my other repo - [test-nginx-vapor](https://github.com/g-mark/test-nginx-vapor) - the setup is much simpler.  This one works under the notion that you want NGiNx to do most of the basic http work like serving static files, forcing https, and serving error pages.  To support this notion there is a single config file that has the common (most likely _all_) of the reverse proxy config - `vapor_proxy.conf.erb` - and the main NGiNx config will include that as needed.\n\nHere's what I mean:\n\n```\n# custom exact location (`/` - welcome)\nlocation = / {\n    include vapor_proxy.conf;\n}\n\n# custom prefix location (`/hello/*` - all hello routes)\nlocation /hello/ {\n    include vapor_proxy.conf;\n}\n\n# custom exact location (`/hi` - forces a 404 from vapor)\nlocation = /hi {\n    include vapor_proxy.conf;\n}\n\n# custom prefix location (`/crash/*` - forces an internal error)\nlocation /crash/ {\n    include vapor_proxy.conf;\n}\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg-mark%2Fcustom-nginx-vapor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fg-mark%2Fcustom-nginx-vapor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg-mark%2Fcustom-nginx-vapor/lists"}