{"id":42577846,"url":"https://github.com/armand1m/golinks","last_synced_at":"2026-01-28T22:01:35.574Z","repository":{"id":42734325,"uuid":"283036787","full_name":"armand1m/golinks","owner":"armand1m","description":"A URL shortener implementation in Next.js and GraphQL.","archived":false,"fork":false,"pushed_at":"2023-02-18T09:47:20.000Z","size":3607,"stargazers_count":21,"open_issues_count":17,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-27T09:39:12.410Z","etag":null,"topics":["golinks","golinks-server","graphql","nextjs","postgraphile","postgresql","react"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/armand1m.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-27T22:59:37.000Z","updated_at":"2025-08-25T16:53:47.000Z","dependencies_parsed_at":"2023-02-06T02:46:14.297Z","dependency_job_id":null,"html_url":"https://github.com/armand1m/golinks","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/armand1m/golinks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armand1m%2Fgolinks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armand1m%2Fgolinks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armand1m%2Fgolinks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armand1m%2Fgolinks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/armand1m","download_url":"https://codeload.github.com/armand1m/golinks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armand1m%2Fgolinks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28853194,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T15:15:36.453Z","status":"ssl_error","status_checked_at":"2026-01-28T15:15:13.020Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["golinks","golinks-server","graphql","nextjs","postgraphile","postgresql","react"],"created_at":"2026-01-28T22:01:34.661Z","updated_at":"2026-01-28T22:01:35.558Z","avatar_url":"https://github.com/armand1m.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# golinks\n\n\u003cdiv style=\"max-width: 700px\"\u003e\n  \u003cimg src=\"./.github/mainpage.png?raw=true\"\u003e\n\u003c/div\u003e\n\n**Mainpage**\n\n\u003cdiv style=\"max-width: 700px\"\u003e\n  \u003cimg src=\"./.github/404.png?raw=true\"\u003e\n\u003c/div\u003e\n\n**404 with Link suggestions**\n\nThis application is deployed at https://go.d1m.dev. Signup is enabled for view only mode.\n\nThis is an implementation of Go Links powered by [Next.js](https://nextjs.org/), [GraphQL](http://graphql.org/) through [PostGraphile](https://www.graphile.org/postgraphile/) and [Auth0](https://www.auth0.com).\n\nIn short, Go Links are a type of URL Shorteners. You can create an alias that points to an URL and will redirect the user to that URL.\n\nPlease check the [Related](#related) section to have a glance on how other companies and universities leverage go links.\n\n## Related\n\n - [golinks.ncsu.edu](https://golinks.ncsu.edu/)\n - [go.middlebury.edu](http://go.middlebury.edu/)\n - [brown.edu/go](https://ithelp.brown.edu/kb/articles/create-a-go-link-shortened-brown-url)\n - [github.com/kellegous/go](https://github.com/kellegous/go)\n - [The quick and simple guide to go links](https://www.trot.to/go-links)\n - [Google's go link culture](https://yiou.me/blog/posts/google-go-link)\n\n## Other implementations\n \n - Open Source: [github/kellegous/go](https://github.com/kellegous/go)\n - Open Source/Freemium: [trot.to](https://www.trot.to/)\n - Freemium: [goatcodes.com](https://goatcodes.com/)\n - Freemium: [golinks.io](https://golinks.io)\n\n## Feature Checklist\n\nThese are just a few ideas that come in my mind.\nPlease feel free to suggest more features by creating an Issue. I'd love to hear your thoughts.\n\nContributions for the following are very welcome.\n\n- [x] Create Links\n- [x] Delete Links\n- [ ] Edit Links\n- [x] Redirect Links\n- [x] Auth\n  - [x] Can be disabled\n  - [x] Powered by Auth0\n- [x] Security\n  - [x] Row Level Security using Auth0 Roles and Permissions\n- [ ] Link Description\n- [x] Link Suggestion on 404\n- [x] Link Usage Metrics\n  - [x] Number: Usage Total Count\n  - [x] Graph: Usage of last 31 days\n- [ ] Link Ownership\n- [x] Link Parameters\n  - For example, a `gh` alias with url `https://github.com/$1/$2` allows `https://go/gh/armand1m/golinks` to be possible.\n- [ ] Link Groups (Folders)\n  - [x] URL: Accept `/` and can be redirected\n  - [ ] UI: Folds URL groups\n- [ ] Private Links\n- [ ] Temporary Links\n- [ ] Random Alias\n- [ ] Help section\n- [ ] Chrome Plugin\n\n## Usage\n\nAliases created can be accessed through your deployment URL + the alias name. _(e.g.: https://go.d1m.dev/twitter redirects to my twitter)_\n\n### Chrome Custom Search Engine\n\nThis allows Chrome to recognize the \"go\" keyword in the address bar. Type \"go\", a space and then the alias for your link.\n\n- Go to [chrome://settings/searchEngines](chrome://settings/searchEngines) \u003e Other search engines \u003e Add\n- **Search engine:** golinks\n- **Keyword**: go\n- **URL with `%s` in place of query:** https://go.mydomain.com/%s\n\n## Deploying\n\nA Docker Image is available at Docker Hub: https://hub.docker.com/r/armand1m/golinks\n\n## Deploying to Kubernetes (GKE + Cloud SQL)\n\n\u003e **NOTE:** This application used to be deployed on GKE with a self-hosted postgres instance. Over time, I've had many issues with both cost of this setup, and the degraded performance. As of 2023, this application is now hosted on https://fly.io, which offered a much simpler and cheaper option to run this application while also being faster and easier to maintain. That said, I kept the kubernetes manifests for future reference, or in case I end up coming back to kubernetes for side projects for any reason :)\n\n\u003e Make sure to change the manifests accordingly to your environment.\n\nCheck the `./kubernetes` folder for k8s manifests content.\nThese manifests deploy the application together with a `cloud_sql_proxy` sidecar to allow networking with Google Cloud SQL.\n\nCreate a secret to keep the connection string:\n\n```sh\nkubectl create secret generic golinks-database \\\n  --from-literal=connectionstring='postgres://\u003cuser\u003e:\u003cpass\u003e@\u003chost\u003e:5432/golinks'\n```\n\nCreate a secret to keep the Cloud SQL service account:\n\n```sh\nkubectl create secret generic cloudsql-service-account \\\n  --from-file=service-account.json=./service-account.json\n```\n\nCreate a secret to keep Auth0 ids and secrets:\n\n```sh\nkubectl create secret generic auth0-properties \\\n  --from-literal=client_id='auth0-app-client-id' \\\n  --from-literal=client_secret='auth0-app-client-secret' \\\n  --from-literal=cookie_secret='random-cookie-secret'\n```\n\nExport needed environment variables for `envsubst`:\n\n```sh\nexport GOOGLE_CLOUD_PROJECT=\u003cgcp-project\u003e\nexport GOOGLE_CLOUD_REGION=\u003cgcp-region\u003e\nexport CLOUDSQL_INSTANCE_NAME=\u003ccloud-sql-instance-name\u003e\nexport HOSTNAME=go.mydomain.com\nexport PROTO=https\nexport LOGONAME=golinks\nexport AUTH0_ENABLED=true\nexport AUTH0_DOMAIN=\u003cauth0-domain\u003e\nexport AUTH0_AUDIENCE=\u003cauth0-audience\u003e\nexport AUTH0_COOKIE_DOMAIN=go.mydomain.com\nexport AUTH0_REDIRECT_URL=https://go.mydomain.com/api/callback\nexport AUTH0_POST_LOGOUT_REDIRECT_URL=https://go.mydomain.com\n```\n\nCreate a deployment and service:\n\n```sh\ncat ./kubernetes/deployment.yaml | envsubst | kubectl apply -f -\nkubectl apply -f ./kubernetes/service.yaml\n```\n\n### Istio\n\n\u003e Make sure to change the manifests accordingly to your environment.\n\nCreate the virtual service and destination rules:\n\n```sh\n# switch for the name of your gateway\nexport ISTIO_GATEWAY_NAME=istio-ingressgateway\nexport HOSTNAME=go.mydomain.com\n\ncat ./kubernetes/istio/virtual-service.yaml | envsubst | kubectl apply -f -\nkubectl apply -f ./kubernetes/istio/destination-rule.yaml\n```\n\n## Authentication\n\nThis app leverages [Auth0](https://auth0.com) as an Identity provider. Auth0 is used to manage users and their permissions to access and modify data in this application.\n\n### Enable Auth0\n\nTo enable, make sure you set the `AUTH0_ENABLED` env var as `true`.\n\nIn case this is set to `false`, every other environment variable prefixed with `AUTH0_` can be considered optional.\n\n### Configuring Auth0\n\n\u003e In the future, these steps will be automated through the Auth0 Provider for Terraform.\n\n**Create a Regular Web Application:**\n\nIt's important that it is a Regular Web Application since this is a Next.js app. It also relies on the `accessToken` being a JWT token, so the server can extract roles and permissions from Auth0.\n\n**Setup callback and logout urls:**\n\nSetup the callback and logout url's to redirect to your domain + the route.\n\nE.g.: \n\nCallback URL: `http://localhost:3000/api/callback`\nPost Logout Redirect URL: `http://localhost:3000`\n\nKeep the `audience`, `domain`, `client_id` and `client_secret` for easy access, as you'll need these to spin up the server (both in development and production)\n\n**Create the following roles and permissions**:\n\nI'm using YAML here to give a better representation of how the permissions should be setup in Auth0 roles:\n\n```yaml\nrole: editor\npermissions:\n- create:golinks\n- update:golinks\n- delete:golinks\n```\n\n```yaml\nrole: viewer\npermissions:\n- read:golinks\n```\n\nThese roles are used by Postgraphile when setting up a transaction for a query in a specific request context. This allows us to leverage Row Level Security through Postgres Policies to avoid access to data in the source of truth.\n\nThese roles are also used in the frontend to avoid rendering features for the user.\n\n**Create an user and assign roles:**\n\nCreate an user and assign both the `editor` and `viewer` roles so you have access to all features.\n\n## Developing\n\n`armand1m/golinks` is a Next.js app using GraphQL.\n\nThe database must be a [Postgres 12.x](http://postgresql.org/) database as the GraphQL API is generated using [Postgraphile](https://www.graphile.org/postgraphile/) and leverages features like Row Level Security only available from Postgres 9.6+.\n\nPostGraphile is then used as a NPM module and served through Next.js routes itself, so you don't have to worry about CORS, and the API is initialized together with the Next.js application.\n\nGraphQL Type definitions are generated on application startup during development, so make sure your database executed the initialization scripts during startup as PostGraphile will infer them to the generate the `type-defs.graphqls` file. (This brings some caveats when making breaking changes in the database schema during development time, but easy to overcome.)\n\n`graphql-let` then is used to generate type definitions in Typescript for development use.\n\n### Local Database without Auth0 in Watch mode:\n\nFor development, we use the official `postgres` docker image. Migrations need to be ran manually using `dbmate` and the SQL scripts provided.\n\nStart the database:\n\n```sh\ndocker-compose up -d db\n```\n\nRun the migrations using [`dbmate`](https://github.com/amacneil/dbmate):\n\n```sh\nexport DATABASE_URL=postgres://dev:dev@127.0.0.1:5432/golinks?sslmode=disable\ndbmate up\n```\n\nRegenerate the `./lib/type-defs.graphqls` with:\n\n```sh\nnpx postgraphile \\\n  --connection 'postgres://dev:dev@127.0.0.1:5432/golinks' \\\n  --schema public \\\n  --export-schema-graphql ./lib/type-defs.graphqls \\\n  --subscriptions \\\n  --dynamic-json \\\n  --no-setof-functions-contain-nulls \\\n  --no-ignore-rbac \\\n  --no-ignore-indexes \\\n  --show-error-stack=json \\\n  --extended-errors hint,detail,errcode \\\n  --append-plugins @graphile-contrib/pg-simplify-inflector \\\n  --enable-query-batching \\\n  --legacy-relations omit \\\n  --no-server\n```\n\nCreate an `.env.local` file (with auth0 disabled):\n\n```sh\ncat \u003e ./.env.local \u003c\u003cEOL\nDATABASE_CONNECTION_STRING=postgres://dev:dev@127.0.0.1:5432/golinks\nDATABASE_SCHEMA=public\nNODE_ENV=development\nAUTH0_ENABLED=false\nPROTO=http\nHOSTNAME=localhost:3000\nLOGONAME=go.localhost\nEOL\n```\nDownload dependencies and run in development mode:\n\n```sh\nyarn\nyarn dev\n```\n\nAccess http://localhost:3000 and you should have a live development environment running.\n\n### Locally, with docker, local db and Auth0:\n\n```sh\ncat \u003e ./.env.local \u003c\u003cEOL\nDATABASE_CONNECTION_STRING=postgres://dev:dev@db:5432/golinks\nDATABASE_SCHEMA=public\nNODE_ENV=production\nAUTH0_ENABLED=true\nAUTH0_DOMAIN=\u003cauth0-domain\u003e\nAUTH0_AUDIENCE=\u003cauth0-audience\u003e\nAUTH0_CLIENT_ID=\u003cauth0-client-id\u003e\nAUTH0_CLIENT_SECRET=\u003cauth0-client-secret\u003e\nAUTH0_COOKIE_SECRET=\u003cauth0-cookie-secret\u003e\nAUTH0_COOKIE_DOMAIN=localhost\nAUTH0_REDIRECT_URL=http://localhost:3000/api/callback\nAUTH0_POST_LOGOUT_REDIRECT_URL=http://localhost:3000\nHOSTNAME=localhost:3000\nPROTO=http\nLOGONAME=go.mydomain.dev\nEOL\n\ndocker-compose up\n```\n\nAccess http://localhost:3000\n\n### Locally, with docker, cloud sql db and Auth0:\n\n```sh\n# Environment Variables for the Application\ncat \u003e ./.env.cloud \u003c\u003cEOL\nDATABASE_CONNECTION_STRING=postgres://\u003cpostgraphile-user\u003e:\u003cpostgraphile-user-password\u003e@db:5432/golinks\nDATABASE_SCHEMA=public\nNODE_ENV=production\nAUTH0_ENABLED=true\nAUTH0_DOMAIN=\u003cauth0-domain\u003e\nAUTH0_AUDIENCE=\u003cauth0-audience\u003e\nAUTH0_CLIENT_ID=\u003cauth0-client-id\u003e\nAUTH0_CLIENT_SECRET=\u003cauth0-client-secret\u003e\nAUTH0_COOKIE_SECRET=\u003cauth0-cookie-secret\u003e\nAUTH0_COOKIE_DOMAIN=localhost\nAUTH0_REDIRECT_URL=http://localhost:3000/api/callback\nAUTH0_POST_LOGOUT_REDIRECT_URL=http://localhost:3000\nHOSTNAME=localhost:3000\nPROTO=http\nLOGONAME=go.mydomain.dev\nEOL\n\n# Environment Variables for the Cloud SQL Proxy\nexport GCP_KEY_PATH=\"~/cloud-sql-service-account.json\"\nexport CLOUDSQL_INSTANCE=\"\u003cgcp-project\u003e:\u003cgcp-region\u003e:\u003ccloud-sql-instance-name\u003e=tcp:0.0.0.0:5432\"\n\ndocker-compose -f ./docker-compose-cloud-sql.yml up\n```\n\n### Cleaning Local Database\n\n```sh\n./clean-local-database.sh\n```\n\n### Building docker image\n\n```sh\ndocker build . -t armand1m/golinks\n```\n\n## License\n\nMIT © [Armando Magalhaes](https://github.com/armand1m)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farmand1m%2Fgolinks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farmand1m%2Fgolinks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farmand1m%2Fgolinks/lists"}