Deno SaaSKit is an open-sourced, highly performant template for building your SaaS quickly and easily.
Note: this project is in beta. Design, workflows, and user accounts are subject to change.
- Deno's built-in formatter, linter and test runner and TypeScript support
- Next-gen web framework with Fresh
- In-built data persistence with Deno KV
- High-level OAuth with Deno KV OAuth
- Modern CSS framework with Tailwind CSS
- Responsive, SaaS-oriented design
- Dashboard with users view and statistics chart
- Stripe integration (optional)
- First-class web performance
- REST API
- Blog with RSS feed and social sharing icons
- HTTP security headers
Before starting, you'll need:
To get started:
- Clone this repo:
git clone https://github.com/denoland/saaskit.git cd saaskit
- Create a new
.env
file. - Navigate to GitHub's New OAuth Application page.
- Set Application name to your desired application name. E.g.
ACME, Inc
. - Set Homepage URL to
http://localhost:8000
. - Set Authorization callback URL to
http://localhost:8000/callback
. - Click Register application.
- Copy the Client ID value to the
.env
file:GITHUB_CLIENT_ID=<GitHub OAuth application client ID>
- On the same web page, click Generate a new client secret.
- Copy the Client secret value to the
.env
file on a new line:GITHUB_CLIENT_SECRET=<GitHub OAuth application client secret>
- Start the server:
deno task start
- Navigate to
http://localhost:8000
to start playing with your new SaaS app.
This guide will enable test Stripe payments, the pricing page, and "Premium user" functionality.
Before starting, you'll need:
- A Stripe account
- The Stripe CLI installed and signed-in on your machine
To get started:
- Navigate to the API keys page on the Developers dashboard.
- In the Standard keys section, click Reveal test key on the Secret key table row.
- Click to copy the value and paste to the
.env
file:STRIPE_SECRET_KEY=<Stripe secret key>
- Run the Stripe initialization script:
deno task init:stripe
- Copy the Stripe "Premium Plan" price ID to the
.env
file:STRIPE_PREMIUM_PLAN_PRICE_ID=<Stripe "Premium Plan" price ID>
- Begin
listening locally to Stripe events:
stripe listen --forward-to localhost:8000/api/stripe-webhooks --events=customer.subscription.created,customer.subscription.deleted
- Copy the webhook signing secret to the
.env
file:STRIPE_WEBHOOK_SECRET=<Stripe webhook signing secret>
- Start the server:
deno task start
- Navigate to
http://localhost:8000
to start playing with your new SaaS app with Stripe enabled.
Note: You can use Stripe's test credit cards to make test payments while in Stripe's test mode.
Use the following commands to work with your local Deno KV database:
deno task db:seed
- Populate the database with data from the Hacker News API.deno task db:dump > backup.json
- Write all database entries tobackup.json
.deno task db:restore backup.json
- Restore the database frombackup.json
.deno task db:reset
- Reset the database. This is not recoverable.
The utils/constants.ts file includes global values used across various aspects of the codebase. Update these values according to your needs.
-
Create a
.md
file in the /posts with the filename as the slug of the blog post URL. E.g. a file with path/posts/hello-there.md
will have path/blog/hello-there
. -
Write the Front Matter then Markdown text to define the properties and content of the blog post.
--- title: This is my first blog post! publishedAt: 2022-11-04T15:00:00.000Z summary: This is an excerpt of my first blog post. --- # Heading 1 Hello, world! ```javascript console.log("Hello World"); ```
-
Start the server:
deno task start
-
Navigate to the URL of the newly created blog post. E.g.
http://localhost:8000/blog/hello-there
.
See other examples of blog post files in /posts.
You can customize theme options such as spacing, color, etc. By default, Deno
SaaSKit comes with primary
and secondary
colors predefined within
tailwind.config.ts
. Change these values to match your desired color scheme.
To replace the cover image, replace the /static/cover.png
file. If you'd like to change the filename, also be sure to change the
imageUrl
property in the <Head />
component.
This section assumes that a local development environment is already set up.
- Navigate to your GitHub OAuth application settings page.
- Set the Homepage URL to your production URL. E.g.
https://hunt.deno.land
. - Set the Authorization callback URL to your production URL with the
/callback
path. E.g.https://hunt.deno.land/callback
. - Copy all the environment variables in your
.env
file to your production environment.
Deploy to Deno Deploy
- Clone this repository for your SaaSKit project.
- Sign into Deno Deploy with your GitHub account.
- Select your GitHub organization or user, repository, and branch.
- Select Automatic deployment mode and
main.ts
as the entry point. - Click Link, which will start the deployment.
- Once the deployment is complete, click on Settings and add the production environmental variables, then hit Save.
You should now be able to visit your newly deployed SaaS.
Docker makes it easy to deploy and run your Deno app to any virtual private server (VPS). This section will show you how to do that with AWS Lightsail and Digital Ocean.
- Install Docker on your machine, which should also
install
the
docker
CLI. - Create an account on Docker Hub, a registry for Docker container images.
Note: the
Dockerfile
,.dockerignore
anddocker-compose.yml
files come included with this repo.
- Grab the SHA1 commit hash by running the following command in the repo's root folder:
# get the SHA1 commit hash of the current branch
git rev-parse HEAD
-
Copy the output of the above and paste it as
DENO_DEPLOYMENT_ID
in your .env file. This value is needed to enable caching on Fresh in a Docker deployment. -
Finally, refer to these guides for using Docker to deploy Deno to specific platforms:
- Activate your Stripe account.
- Navigate to the API keys page on the Developers dashboard.
- In the Standard keys section, click Reveal test key on the Secret key table row.
- Click to copy the value and paste to your
STRIPE_SECRET_KEY
environment variable in your production environment.STRIPE_SECRET_KEY=<Stripe secret key>
- Navigate to the Webhooks page to register your webhook endpoint.
- Click Add endpoint.
- Set Endpoint URL to your production URL with the
/api/stripe-webhooks
path. E.g.https://hunt.deno.land/api/stripe-webhooks
. - Set Listen to to
Events on your account
. - Set
customer.subscription.created
andcustomer.subscription.deleted
as events to listen to. - Click Add endpoint.
- Optionally, set up your Stripe branding to customize the look and feel of your Stripe checkout page.
Set GA4_MEASUREMENT_ID
in your production environment to enable Google
Analytics.
Note: it is not recommended to set this locally, otherwise your tests and debugging requests will be logged.
Get all items in chronological order. Add ?cursor=<cursor>
URL parameter for
pagination. Limited to 10 items per page.
Example 1:
// https://hunt.deno.land/api/items
{
"values": [
{
"id": "01HAY7A4ZD737BHJKXW20H59NH",
"userLogin": "Deniswarui4",
"title": "czxdczs",
"url": "https://wamufx.co.ke/",
"score": 0
},
{
"id": "01HAD9KYMCC5RS2FNPQBMYFRSK",
"userLogin": "jlucaso1",
"title": "Ok",
"url": "https://github.com/jlucaso1/crunchyroll-quasar",
"score": 0
},
{
"id": "01HA7YJJ2T66MSEP78NAG8910A",
"userLogin": "BrunoBernardino",
"title": "LockDB: Handle process/event locking",
"url": "https://lockdb.com",
"score": 2
}
// 7 more items...
],
"cursor": "AjAxSDdUNTBBUkY0QzhEUjRXWjkyVDJZSFhZAA=="
}
Example 2 (using cursor
field from page 1):
// https://hunt.deno.land/api/items?cursor=AjAxSDdUNTBBUkY0QzhEUjRXWjkyVDJZSFhZAA==
{
"values": [
{
"id": "01H777YG17VY8HANDHE84ZXKGW",
"userLogin": "BrunoBernardino",
"url": "https://asksoph.com",
"title": "Ask Soph about a dead philosopher",
"score": 2
},
{
"id": "01H6RG2V3AV82FJA2VY6NJD9EP",
"userLogin": "retraigo",
"url": "https://github.com/retraigo/appraisal",
"title": "Appraisal: Feature Extraction, Feature Conversion in TypeScript",
"score": 0
},
{
"id": "01H64TZ3TNKFWS35MJ9PSGNWE1",
"userLogin": "lambtron",
"url": "https://www.zaynetro.com/post/2023-how-deno-works",
"title": "How Deno works (blog post)",
"score": 2
}
// 7 more items...
],
"cursor": "AjAxSDJUSlBYWUJRM1g0OEo2UlIzSFgyQUQ0AA=="
}
Get the item with the given ID.
Example:
// https://hunt.deno.land/api/items/01H5379J1VZ7EB54KSCSQSCRJC
{
"id": "01H5379J1VZ7EB54KSCSQSCRJC",
"userLogin": "lambtron",
"url": "https://github.com/Savory/saaskit-danet",
"title": "saaskit-danet: a modern SaaS template built for Fresh for SSR and Danet for the API",
"score": 10
}
Get all users in alphabetical order by GitHub login. Add ?cursor=<cursor>
URL
parameter for pagination. Limited to 10 users per page.
Example 1:
// https://hunt.deno.land/api/users
{
"values": [
{
"login": "51chengxu",
"sessionId": "9a6745a1-3a46-45c8-a265-c7469ff73678",
"isSubscribed": false,
"stripeCustomerId": "cus_OgWU0R42bolJtm"
},
{
"login": "AiridasSal",
"sessionId": "adb25cac-9be7-494f-864b-8f05b80f7168",
"isSubscribed": false,
"stripeCustomerId": "cus_OcJW6TadIjjjT5"
},
{
"login": "ArkhamCookie",
"stripeCustomerId": "cus_ObVcWCSYwYOnWS",
"sessionId": "fd8e7aec-2701-44ae-925b-25e17ff288c4",
"isSubscribed": false
}
// 7 more users...
],
"cursor": "AkVob3ItZGV2ZWxvcGVyAA=="
}
Example 2 (using cursor
field from page 1):
// https://hunt.deno.land/api/users?cursor=AkVob3ItZGV2ZWxvcGVyAA==
{
"values": [
{
"login": "EthanThatOneKid",
"sessionId": "ae7425c1-7932-412a-9956-e456787d557f",
"isSubscribed": false,
"stripeCustomerId": "cus_OeYA2oTJRlZBIA"
},
{
"login": "Fleury99",
"sessionId": "2e4920a3-f386-43e1-8c0d-61b5e0edfc0d",
"isSubscribed": false,
"stripeCustomerId": "cus_OcOUJAYmyxZlDR"
},
{
"login": "FriendlyUser",
"stripeCustomerId": "cus_ObLbqu5gxp0qnl",
"sessionId": "508ff291-7d1c-4a67-b19f-447ad73b5914",
"isSubscribed": false
}
// 7 more users...
],
"cursor": "Ak5ld1lhbmtvAA=="
}
Get the user with the given GitHub login.
Example:
// https://hunt.deno.land/api/users/hashrock
{
"login": "hashrock",
"stripeCustomerId": "cus_ObqbLXkRtsKy70",
"sessionId": "97eec97a-6636-485e-9b14-253bfa3ce1de",
"isSubscribed": true
}
For the user, the website should be fast, secure and have a design with clear intent. Additionally, the HTML should be well-structured and indexable by search engines. The defining metrics for these goals are:
- A perfect PageSpeed Insights score.
- Fully valid HTML, as measured by W3C's Markup Validation Service.
For the developer, the codebase should minimize the steps and amount of time required to get up and running. From there, customization and extension of the web app should be simple. The characteristics of a well-written codebase also apply, such as:
- Easy to understand
- Modular functionality
- Clearly defined behavior with validation through tests
Join the #saaskit
channel in Deno's Discord to meet
other SaaSKit developers, ask questions, and get unblocked.
Here's a list of articles, how to guides, and videos about SaaSKit: