


























Software as a Service (SaaS) is a business where you provide value to your users using your software without them having to install or maintain it. A SaaS doesn’t have to be a full-blown software suite but it can also be as simple as a Slack Bot or a ToDo app.
Building a SaaS business is one of the most lucrative avenues for software engineers because it provides:
In this tutorial, you’ll build a SaaS application for a job application board and deploy it on the web to your custom domain.
Before starting development it’s important to understand the project requirements and expectations. It is also important to start with the most basic requirements for the Minimal Viable Product (MVP) to test your SaaS idea.
This job board application has the following requirements:
In terms of features, the application will have the following pages:
Now that the feature specifications are done. You can choose a tech stack that provides the best developer experience to build it. The following technologies match the requirements laid out previously:
To get started, create a Remix project by running the following command in the CLI:
npx create-remix@latest
This is an interactive command that will bootstrap a Remix project with TailwindCSS and TypeScript. Choose the following options when prompted:
job-board.You’ll use shadcn/ui as the UI library for consistent design and fast development. To get started, run the following command.
npx shadcn@latest init
This is an interactive command that will set up shadcn/ui with all its dependencies. Choose the following options when prompted:
Now install the components you’ll be using to build the application by running this command in your project:
npx shadcn@latest add button input textarea card label separator
You’ll use PostgreSQL to store the application data such as the job posts and applicants. The following diagram gives an overview of the data model for this application:

To integrate the Postgres database, you’ll use Drizzle ORM with postgres.js driver that lets you use Postgres in the TypeScript application. Follow these steps to get started with Drizzle:
npm i drizzle-orm nanoid postgres in the project command line to install the dependencies.npm i -D drizzle-kit dotenv to add utilities for development.app/db.server/index.ts to initialize the drizzle orm:import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import "dotenv/config";
const queryClient = postgres(String(process.env.DATABASE_URL));
export const db = drizzle(queryClient);
.servera naming convention in Remix used to explicitly exclude files from the client-side JS bundle.
app/db.server/schema.ts with the schema definitions and export the schema types.drizzle.config.ts at the project root to configure drizzle credentials.package.json file: "scripts": {
// other scripts..
"generate": "drizzle-kit generate",
"migrate": "drizzle-kit push"
},
These commands will come in handy for managing database schema migrations. To learn more about schema migrations, see Drizzle migrations fundamentals.
npm run generate to generate the first migration file. This should create a new drizzle folder in the project. You should check this into your Git project.For local development you need to run Postgres on your computer, you can use Docker for it. To follow the next step, you must have docker installed on your computer.
docker run -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=mysecretpassword -e POSTGRES_DB=pgdb -p 5432:5432 -d postgres
.env file in your project and add DATABASE_URL variable to it:DATABASE_URL=postgres://admin:mysecretpassword@127.0.0.1:5432/pgdb
Make sure that the env file is not checked into your Git project.
npm run migrate to apply the schema migrations to the local database.You’re now all set to start working with the local Postgres database.
To get started, sign up for a Clerk account if you don’t have one and then login into your Clerk dashboard. Then follow these steps:
.env file.Inside your project follow these steps to add authentication using Clerk:
npm install @clerk/remix in the terminal to install the dependencies.app/root.tsx file to wrap the application in the ClerkApp higher order component. This ensures that the application has access to the authentication context and utilities from Clerk.routes/sign-up.$.tsx to render the sign up page using the SignUp component.routes/sign-in.$.tsx to render the sign in page using the SignIn component..env file for server-side auth routing:CLERK_SIGN_IN_URL=/sign-in
CLERK_SIGN_UP_URL=/sign-up
CLERK_SIGN_IN_FALLBACK_URL=/
CLERK_SIGN_UP_FALLBACK_URL=/
This will configure Clerk authentication provider and utilities throughout the application, which you’ll use in the next steps.
app/routes/_index.tsx file with the code to show all the available jobs to the visitor.This code fetches the jobs from the database using the loader function and displays them to the user.

To render the job details markdown content as HTML, install the showdown package.
npm i showdown
npm i -D @types/showdown
Now add the typography plugin for Tailwind CSS to apply styles for markdown content.
npm install -D @tailwindcss/typographytailwind.config.ts file as follows:/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
// ...
},
plugins: [
require('@tailwindcss/typography'),
// ...
],
}
app/routes/jobs.$jobId.tsx.This file will create the route for job post details from where the visitor can apply for the job.
The $ in the file name creates a dynamic route and lets you access the jobId param at runtime.
The loader function provides the job details data and creates the HTML markup for the markdown content. The action function handles the POST request from the job application <Form> and then saves the resume (PDF file) to the server disk and the applicant details in the database.

The user journey for a hiring manager will be as follows:

app/routes/dashboard.tsx for the dashboard layout.
This file acts as the root of the dashboard and handles the authentication check on the server side data loader. It redirects the user to the /sign-in page if they’re not logged in. The <Outlet /> component renders all the child or nested routes under the dashboard.app/routes/dashboard._index.tsx file for the dashboard home page and display all the job posts created by the logged in hiring manager.app/routes/dashboard.new.tsx file for the /dashboard/new route.
This is the page from where the hiring managers will create new job posts.The page will render as shown in the following image:

To let the hiring managers manage their job posts -
app/routes/dashboard.$jobId.tsx with a nested layout to display the /edit and /applicants pages.The edit job post page is similar to the new job post page but pre-filled with the default form values.
app/routes/dashboard.$jobId.edit.tsx with the code to fetch job data with the jobId and pre-fill edit job post form with default values.
To display the applicants for a job post -
app/routes/dashboard.$jobId.applicants.tsx.The loader function fetches the list of applicants for the current job post and lets the hiring manager download their resumes. The Download resume button is a download link to a route you’ll learn about in the next steps.

To let the users download the resumes, create a resource route in that only has a loader and returns a file response.
app/routes/dashboard.$jobId.$applicantId.resume.ts](https://github.com/anshumanbhardwaj/job-board/blob/main/app/routes/dashboard.%24jobId.%24applicantId.resume.tsx)xIn this file the loader function reads the resume file from the disk and returns it as the response.
The MVP application is now ready and it’s time to deploy it. To deploy this application you can choose any cloud provider but setting up a virtual private server (VPS) is rather complicated and time-consuming to start with. To get started quickly, you can use a platform like Railway that manages the low-level details of application deployment and exposes a seamless developer experience.
Before proceeding
package.json to run database migration in production before starting the server: "scripts": {
// other scripts...
"railway": "npm run migrate && npm run start"
},
To deploy your application to Railway follow these steps:
This will create a new project with an application service and an automatic deployment will start. Railway will now automatically deploy the application whenever there’s a new commit on the main branch of your GitHub repository.

npm run railway script you created earlier.
The deployment will crash because it’s missing the important credentials such as the DATABASE_URL and Clerk configuration. Close the job-board service pane.
The application stores applicant resumes on the server disk but Railway services don’t have persistent volumes by default. To fix this, follow these steps:
/resumes . This is because /resumes is the directory that the file upload action is using.A persistent volume is now attached to your service.
To create a Postgres database in this project, follow these steps:
This will add a PostgreSQL service to your project.

To add the environment variables, follow these steps:
.env file and paste in the raw editor dialog.For brevity, this article is using the Clerk development credentials. You should change those before going to production. To learn more about deploying Clerk authentication to production, refer to Clerk documentation
DATABASE_URL variable, use the Reference Variables from the Postgres service, as shown in the following image.
Changing the environment variables will trigger automatic deployment. After the deployment finishes, you’ll see that the application is running successfully.
To access the application, you need the service domain.
After a few seconds, a random and unique domain will be assigned to your service.

You can now access the application on the internet using this URL.

Now that the application is up and running on the internet it’s time to add a custom domain that is easy to remember and share. For this application, you’ll be using Cloudflare for DNS setup as well as the CDN proxy because it provides:
To add a custom domain to your Railway application, follow these steps:


@ if you’re using root domain.To use Cloudflare proxy, you must activate end-to-end encryption.
After saving all changes, your Railway service should detect the custom domain and Cloudflare proxy, as shown in the following image.

You can now access the application on your custom domain.

After completing this tutorial, you will have successfully built and deployed a SaaS application with Remix, PostgreSQL, and Railway. This application is a good starting point from which you can start your SaaS journey and build on top of it as you go.
In the next tutorial, you’ll learn about scaling a SaaS using Railway, including cost management, multiple environments, and continuous deployments.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。