A basic starter for universally rendered (if you want) graphql-powered sites using NextJS, Auth0, and Apollo.
This is a basic, highly opinionated starter that allows you to very quickly build React apps with authentication, authorization, a good API, and universal rendering out of the box. Before using this project, you should be aware that it has not been hardened for production use, and is more an exploration of a few emerging philosophies than a working toolkit. There are a number of tradeoffs that come with it. In other words: use at your own risk.
The main goal of this project is to make it possible for a single developer to build a complex, service-oriented-style app that can be expanded later, while still feeling as if they're working on a monolithic-style, easy-to-reason stack. A few new technologies, chief among them GraphQL, make achieving this flexibility newly possible.
This was built by a someone with limited daily professional development experience, but a fair amount of computer science education and application architecture experience. Thus, it is designed to solve common headaches that come from building entire apps by yourself. Among them:
The project uses NextJS's out-of-the-box development server with hot-swapping and Netlify's local lambda environment. The goal was to take away the pain of self-managing a tangle of Babel, Webpack, and other toolchains while allowing developers the ability to hook into those configurations if needed.
The project uses Apollo/GraphQL to tightly couple data to individual components with a central store managed behind the scenes by Apollo. The goal, bluntly, was to take away the pain of Redux. Redux was a vast improvement over previous flux architecture, but is still painful for an individual developer to manage when apps get more complex. The combination of Apollo and React Context makes self-managing a single atom/store with reducers unnecessary.
The project encourages "owning" as much as your own stack as feasible, while still being simple to manage. It's not a complete success. It relies entirely on Auth0 for authentication. Other than that, it can consume any GraphQL endpoint, and be hosted on just about any platform.
The project encourages the use of a GraphQL service like Hasura or Prisma. These projects, combined with hosting services like Heroku, Digital Ocean, and Amazon Lightsail, make it extremely simple to design and deploy scalable, performant APIs with authorization with little configuration or server management required. The goal was twofold: First, to avoid the pain of managing a REST API, which is the chief source of complexity for many single developers attempting to build service-oriented-style apps. Second, to avoid the pain of writing your own resolvers, which is a source of complexity for more barebones graphQL engines. Using modern GraphQL engines comes with a tradeoff, however. They drastically simplify the movement of data from database to front-end component by eliminating the need to translate from database structures to server-side structures to API endpoint structures. In so doing, they remove the ORM metaphors many developers are familiar with, instead translating directly from relational database best practices to graphs. Thus, developers should have a basic understanding of relational database practices before working with these systems.
The project uses NextJS's deployment tools so it can be built and deployed as a server-side-only rendered app (SSR), a client-side single page application (SPA), a universal app, or all of the above at once. The project can be easily deployed on Heroku, Netlify, Amazon AWS/Lightsail, Digital Ocean, and beyond with minimal configuration.
Hasura's event triggers, combined with Netlify Functions, AWS Lambda, or external services, drastically reduce the complexity needed to manage background processes. For many tasks, instead of running a tool like Kue or Bull, you can simply create an Event Trigger in the Hasura console that calls a webhook to run a background process. Hasura's event triggers include retry and robust failure logging out of the box, eliminating the need for queues in many cases. Because Hasura's event triggers use built-in postgres triggers, you can even update the database from other places and automatically spin off jobs. To schedule jobs, you can use a web-based cron service like AWS Fargate, Google Cloud Scheduler, Heroku Scheduler, or simply IFTTT or Zapier to trigger the same webhooks.
If you need to implement more robust queuing in the future (for example: cancelling jobs), you can easily deploy a server with Bull or add a service like CloudAMQP, rewriting your event trigger functions to insert messages into those queues instead of running jobs directly.
The downside to this approach is that at the moment, Hasura event triggers cannot be deployed or managed by the cli. Presumably, they'll address this limitation in a future update.
- NextJS, for the basic React-based universal framework
- Express, for custom server routes if running server-side
- Apollo, for the GraphQL client
- Auth0, for authentication
- Blueprint, for components and basic styles
- ESLint, for linting/static analysis
- Netlify-Lambda for static functions
The project is by no means complete. It's missing quite a few pieces that are required for most projects. Among them:
- More intelligent state management/hydration for bootstrapping the client in SSR apps via Apollo.
- Server-side authorization.
- Clearer understanding of server-side and client-side parts of the app, in particular NextJS's getInitialProps.
- Universal server-side functions that can run via express or via Lambda. I.E., if you don't intend to use Netlify or AWS Lambda, you'll need to develop Express functions served by NextJS.
- Better development environment database support.
- Offline mode.
- Any testing at all.
- Make sure you have the version of node specified in .node-version installed on your machine. I use nodenv to help with this.
- Grab modules by running
npm install
- Copy
example.env
to.env
and set the proper credentials. - If using Hasura, copy
db/config.example.yaml
todb/config.yaml
and add the proper credetnails. - If using Hasura, install the CLI.
- If using Hasura, apply the first migrations with
hasura migrate apply
.
- Run
npm run dev
- The site will be available at
http://localhost:3000
- Run
npm run dev:functions
- The site will be available at
http://localhost:3000
- Netlify lambda functions will be available at
http://localhost:9000/function-name
- Run
npm run export
- cd to
out
directory - Use a tool like
serve
or similar viaserve -p 8080
and browse tohttp://localhost:8080
to see static site
- run
cd db
- run
hasura console
- Any modifications made via the console will be saved as migrations.
This describes the deployment configurations I've tried, all of which use static generation. But NextJS is simply an Express app. If you intend to use it for SSR, it can be deployed almost anywhere that supports Node/Express.
- Create a new site from your repo.
- Set the build command to
npm run export
. - Set the serving directory to
out
. - Set the environment variables for
API_URL
,AUTH_CLIENT_ID
,AUTH_DOMAIN
, andSITE_URL
. - Trigger a build manually or via a new git push.
Hasura can be deployed on top of any postgres database, but it's especially nice because it can be installed with one click to Heroku or Digital Ocean, both of which I've used. More info in their docs. From there, securing the endpoint is relatively trivial either via environment variables for Heroku, or a config file for configurations that use Docker instances, like Digital Ocean.
The starter uses a combination of dot-env and NextJS runtime config to make sure no sensitive environment variables leak to the client. By default, no variables are available to client that aren't explicitly set in next.config.js. Environment variables set upstream (ie on Heroku or Netlify) will automatically replace environment variables set in .env, so make sure to set your variables in those locations.
Code placed in functions/src
will be automatically packaged and built when you run npm run dev:functions
or npm run export
.
These functions resemble AWS Lambda, returning a callback with status and body information.
For more documentation about functions, see Netlify's docs.
I'm honestly not a big fan of auth0, for a bunch of reasons. But it works. One big issue they currently have is there is no way to keep someone logged in on the client on Safari without setting up a custom domain at the same root as your site. See: https://auth0.com/docs/api-auth/token-renewal-in-safari