Monorepo boilerplate that comes with all the basic SaaS features that you need to get up and running.
- A Next.js app where users can log in, handle their Stripe subscription and update their email, password etc.
- A statically generated blog with articles written with MDX
- A GraphQL API built with TypeGraphQL, Prisma and Apollo server. Includes webhooks to handle changes to a user's subscription!
Stop wasting time setting up the same things over and over and start building!
- π Authentication using Ory Kratos
- π° Subscriptions with Stripe
- π» Pretty UI with light / dark mode using Chakra UI
- π Typesafe ORM with Prisma
- π GraphQL API using TypeGraphQL & Apollo
- βοΈ Fetching, caching and updating asynchronous data with React Query
- π Blog using MDX
- π Internationalisation (i18n) using next-translate
- π SEO with Next SEO
- πΏ CI / CD using GitHub Actions
- π₯ E2E tests with Cypress & React Testing Library
- π Unit & integration tests with Jest
- π UI component explorer with Storybook
- βοΈ Error tracking with Sentry
- π Logging, GraphQL codegen, git hooks for linting, Prettier, ESLint etc.
- Features
- Demo
- Structure
- Running it locally
- Testing
- Deployment
- Miscellaneous
- Roadmap
- Contributing
- Inspiration
Unfortunately the redirects to the blog app aren't working correctly when deployed (not sure why). Also, since I am hosting the API on a free Heroku dyno I can't put the site app and API on the same domain, which means Ory cookies aren't being received, so authenticatted endpoints don't work (I think).
Nx is used to scaffold this monorepo, so all apps can be found under /apps
(api
, blog
, site
) and all libs under /libs
. I sort of tried to follow Nrwl's recommendations for categorising libraries, so read that to get a better understanding. In general, most libraries are split by app, e.g. GraphQL resolvers can be found in the /api/data-access
library, the site's UI components in /site/ui
and so on. Shared libraries can be found under /shared
and single-purpose libraries (e.g markdown
) have their own folder.
There are four apps you need to set up to get everything up and running: the api, site, blog and Ory Kratos. Each section below will detail how to set up each of them separately, but first run
npm i
Apart from environment variables, which will be detailed in each section below, there is also a configuration file found in shared-configuration.ts
which should be filled in as you see fit.
The API is built with Apollo Server, TypeGraphQL and Prisma. To get it up an running you need to set the following
environment variable in your .env
file:
# e.g. mysql://admin:password@localhost:3306/boilerplate
DATABASE_URL=""
# https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY=""
STRIPE_WEBHOOK_SECRET_LIVE=""
# https://docs.sentry.io/platforms/node/
SENTRY_AUTH_TOKEN=""
SENTRY_ORG=""
SENTRY_PROJECT=""
# You can also use Ory Cloud URL here
ORY_SDK_URL="http://127.0.0.1:4433"
Of course you will need to replace this with your own tokens, so e.g. the URL of a MySQL database you have running locally. Best practices on how to use Prisma with PlanetScale can be found in this video.
Once set up, you will need to run the migrations and start the server
prisma migrate deploy
nx serve api
It is possible you may need to run prisma generate
to emit TypeGraphQL types and CRUD resolvers from the Prisma schema.
You can seed the database with
prisma db seed
It uses the seed.ts
file found in the apps/api/src/prisma
folder and assumes you have some products and prices (shown on the pricing page) set up in Stripe. It also creates a user for the E2E tests, but this is explained in E2E testing section.
The site & blog apps are built with Next.js and try to use Next.js' multi zones feature so you can develop and deploy both apps independently. The following environment variables for the site app (apps/site/.env
) are needed
# For when a user wants to update their subscription
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=""
# https://docs.sentry.io/platforms/javascript/guides/nextjs/
SENTRY_DSN=""
# These are already defined in the .env file in your root folder, but will be needed when deploying to Vercel for example
SENTRY_AUTH_TOKEN=""
SENTRY_ORG=""
SENTRY_PROJECT=""
# We need to redirect /blog to where the blog app is deployed
NEXT_PUBLIC_BASE_URL_BLOG="http://localhost:4200"
# API
NEXT_PUBLIC_BASE_URL_API=""
# Used in /api/.ory/[...paths], can also use Ory Cloud URL here
ORY_SDK_URL="http://127.0.0.1:4433"
You can then run the site with
nx serve site
Unfortunately the multi zones feature is not working 100% correctly because of a few issues (i18n is broken because of basePath
and shared components), so it may be possible the blog app will have to be moved into the site app. I've raised the issue in multiple places ([1, 2, 3]) and others have as well, but unfortunately have not received an answer yet. I hope the Vercel teamn will address it soon, or maybe someone out there will be able to
The code for the blog is based on the "Building a blog with Next.js and Nx" series by @juristr, so if you want to know more about how it works, then I recommend you check it out.
Simply put, all blog posts can be found in the articles
folder and adding a .mdx
file there will create a new blog post when building the blog app. This is done using next-mdx-remote, which loads the blog posts from the articles
folder through getStaticProps
in [slug].tsx
.
The only environment variable needed (in production) for the blog app is
NEXT_PUBLIC_BASE_URL_SITE=""
You can then run the blog with
nx serve blog
For authentication Ory Kratos is used, which handles login, registration, verification, account recovery etc. (and you can host it yourself)! To set it up you can either use Ory Cloud or run Ory Kratos locally by cloning their repository and using docker-compose
git clone https://github.com/ory/kratos.git
cd kratos
git checkout v0.8.0-alpha.3
docker-compose -f quickstart.yml -f quickstart-standalone.yml up --build --force-recreate
# If you have SELinux, run:
docker-compose -f quickstart.yml -f quickstart-selinux.yml -f quickstart-standalone.yml up --build --force-recreate
If you decide to run it locally then you will want to remove the name
property from contrib/quickstart/kratos/email-password/identity.schema.json
and update contrib/quickstart/kratos/email-password/kratos.yml
to
version: v0.7.1-alpha.1
dsn: memory
serve:
public:
base_url: http://127.0.0.1:4433/
cors:
enabled: true
allowed_origins:
- http://localhost:3000
- http://localhost:3000/
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
allowed_headers:
- Authorization
- Cookie
exposed_headers:
- Content-Type
- Set-Cookie
admin:
base_url: http://kratos:4434/
selfservice:
default_browser_return_url: http://localhost:3000/
whitelisted_return_urls:
- http://localhost:3000
methods:
password:
enabled: true
flows:
error:
ui_url: http://localhost:3000/error
settings:
ui_url: http://localhost:3000/settings
privileged_session_max_age: 15m
recovery:
enabled: true
ui_url: http://localhost:3000/recovery
verification:
enabled: true
ui_url: http://localhost:3000/verification
after:
default_browser_return_url: http://localhost:3000/
logout:
after:
default_browser_return_url: http://localhost:3000/login
login:
ui_url: http://localhost:3000/login
lifespan: 10m
registration:
lifespan: 10m
ui_url: http://localhost:3000/registration
after:
password:
hooks:
- hook: session
log:
level: debug
format: text
leak_sensitive_values: true
secrets:
cookie:
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
cipher:
- 32-LONG-SECRET-NOT-SECURE-AT-ALL
ciphers:
algorithm: xchacha20-poly1305
hashers:
argon2:
parallelism: 1
memory: 128MB
iterations: 2
salt_length: 16
key_length: 16
identity:
default_schema_url: file:///etc/config/kratos/identity.schema.json
courier:
smtp:
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
While not everything is covered by tests, I have done my best to include tests for the most important parts of the boilerplate. To run tests for a particular app or library you can use the following command
# nx test <name>, e.g.
nx test api
To run E2E tests you first need to create a test user by logging in to the website and then update the seed.ts
file found in the apps/api/src/prisma
with the user's ID and email (I don't know a better way of doing this, sorry π). Once you have done this, you can run the E2E tests with
# nx e2e <name>, e.g.
nx e2e site
CD has not been set up (yet), but each section below will describe what you can do to deploy each app.
You can build a Docker image with the command
docker build -f ./apps/api/Dockerfile . -t api
Deploying to Heroku is very simple, as seen here.
# Where <app> is your Heroku app
docker tag api registry.heroku.com/<app>/web
docker push registry.heroku.com/<app>/web
# Create a new release
heroku container:release web --app <app>
Deploying to Render is even more simple and is where the live demo's API is deployed. It is so simple there isn't really any need for me to explain it here - just log in and see how easy it is for yourself.
I've not deployed the API anywhere else (e.g. AWS, DigitalOcean), but if I do I will update this section. As for the database: I am using PlanetScale.
I've not deployed Ory Kratos yet myself, so I'm not sure exactly how to do it. There is a guide on how to take Kratos to production, but it is unfortunately still under development. In the meantime you can use Ory Cloud or try to figure out something yourself (and update this section after π).
For deploying to Vercel you can use Nx's guide, which shows how straightforward it is. A script for ignoring the build step is found in /tools/ignore-vercel-build-<app>
.
To automatically generate the sitemap make sure to include the postbuild
target in the build script, like so:
npx nx build site --prod && npx nx postbuild site
You may also need to run prisma generate
after running npm install
.
I'm currently looking into a way to deploy the blog with Cloudflare and the site on AWS Lambda@Edge via Serverless Components and will update the section once I have done so.
Chakra UI is great and allows you to update your theme very easily. If you want to change the primary colour of the site and blog apps, then simply header over to libs/shared/theme
and update the primary colour in colors.ts
. If you want to further customise the theme, I recommend reading their guide.
To run the shared Storybook you can use the following command
nx serve shared-storybook:storybook
Instead of Apollo Client or urql, I wanted to use React Query since I had heard really good things about it. Inspired by this discussion React Query can be used with codegen in the following way:
export const ProductsQuery = gql(/* GraphQL */ `
query Products {
products {
id
}
}
`);
...
const { data } = useGraphqlQuery(ProductsQuery);
For more information about how it works, see the amazing gql-tag-operations-preset
. This preset generates typings for your inline gql
function usages, without having to manually specify import statements for the documents. All you need to do is import your gql
function and run codegen in watch mode!
All i18n files can be found under libs/site/i18n
, libs/blog/i18n
and libs/shared/i18n
. Both the site and blog use next-translate, as seen in their respective i18n.js
files. To sync the JSON files you can use the command npm run i18n
.
You can generate assets for the site using the asset-generator
command. It uses the pwa-asset-generator
package to automatically generate icon and splash screen images, favicons and mstile images.
Of course this boilerplate is not perfect: things could have probably been implemented in a better way and certain things are missing. I am going to try and keep everything up to date, improve existing features and add more features. Some things that are on my mind:
- Add E2E tests for CI
- Fix
basePath
,i18n
and other problems with Next.js multi zones feature (site, blog) - Add more guides for deploying
- Add Github Action for CD
- Fix internationalisation for Ory Kratos
- Set up SMTP, mailing list etc.
I would like to make this boilerplate the best it can be, so if you have any suggestions, problems running apps, issues etc. then please don't hesitate to create an issue or even a pull request!
I am terrible at design so I've taken a lot of inspiration from these places: