A modern SaaS template built on Fresh.
See the code⚠️ SaaSKit is no longer actively maintained. ⚠️
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.
Before starting, you'll need:
To get started:
git clone https://github.com/denoland/saaskit.git
cd saaskit
.env file.ACME, Inc.http://localhost:8000.http://localhost:8000/callback..env file:
GITHUB_CLIENT_ID=<GitHub OAuth application client ID>
.env file on a new line:
GITHUB_CLIENT_SECRET=<GitHub OAuth application client secret>
deno task start
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:
To get started:
.env file:
STRIPE_SECRET_KEY=<Stripe secret key>
deno task init:stripe
.env file:
STRIPE_PREMIUM_PLAN_PRICE_ID=<Stripe "Premium Plan" price ID>
stripe listen --forward-to localhost:8000/api/stripe-webhooks --events=customer.subscription.created,customer.subscription.deleted
.env file:
STRIPE_WEBHOOK_SECRET=<Stripe webhook signing secret>
deno task start
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 to
backup.json.deno task db:restore backup.json - Restore the database from backup.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.
https://hunt.deno.land./callback path. E.g. https://hunt.deno.land/callback..env file to your production
environment..github/workflows/deploy.yml file as needed. Hints are in the
file.main.ts as the entry point.You should now be able to visit your newly deployed SaaS website.
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.
docker CLI.Note: the
Dockerfile,.dockerignoreanddocker-compose.ymlfiles come included with this repo.
# 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:
STRIPE_SECRET_KEY environment
variable in your production environment.
STRIPE_SECRET_KEY=<Stripe secret key>
/api/stripe-webhooks
path. E.g. https://hunt.deno.land/api/stripe-webhooks.Events on your account.customer.subscription.created and customer.subscription.deleted as
events to listen to.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 /api/itemsGet 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 /api/items/:idGet 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 /api/usersGet 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 /api/users/:loginGet 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:
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:
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:
771 followers · starred May 2023
164 followers · starred May 2023
1,977 followers · starred Apr 2023
689 followers · starred Aug 2023
A modern SaaS template built on Fresh.
See the code⚠️ SaaSKit is no longer actively maintained. ⚠️
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.
Before starting, you'll need:
To get started:
git clone https://github.com/denoland/saaskit.git
cd saaskit
.env file.ACME, Inc.http://localhost:8000.http://localhost:8000/callback..env file:
GITHUB_CLIENT_ID=<GitHub OAuth application client ID>
.env file on a new line:
GITHUB_CLIENT_SECRET=<GitHub OAuth application client secret>
deno task start
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:
To get started:
.env file:
STRIPE_SECRET_KEY=<Stripe secret key>
deno task init:stripe
.env file:
STRIPE_PREMIUM_PLAN_PRICE_ID=<Stripe "Premium Plan" price ID>
stripe listen --forward-to localhost:8000/api/stripe-webhooks --events=customer.subscription.created,customer.subscription.deleted
.env file:
STRIPE_WEBHOOK_SECRET=<Stripe webhook signing secret>
deno task start
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 to
backup.json.deno task db:restore backup.json - Restore the database from backup.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.
https://hunt.deno.land./callback path. E.g. https://hunt.deno.land/callback..env file to your production
environment..github/workflows/deploy.yml file as needed. Hints are in the
file.main.ts as the entry point.You should now be able to visit your newly deployed SaaS website.
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.
docker CLI.Note: the
Dockerfile,.dockerignoreanddocker-compose.ymlfiles come included with this repo.
# 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:
STRIPE_SECRET_KEY environment
variable in your production environment.
STRIPE_SECRET_KEY=<Stripe secret key>
/api/stripe-webhooks
path. E.g. https://hunt.deno.land/api/stripe-webhooks.Events on your account.customer.subscription.created and customer.subscription.deleted as
events to listen to.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 /api/itemsGet 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 /api/items/:idGet 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 /api/usersGet 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 /api/users/:loginGet 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:
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:
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:
771 followers · starred May 2023
164 followers · starred May 2023
1,977 followers · starred Apr 2023
689 followers · starred Aug 2023