Installation
Get up and running with the spartan/stack.
Setting up your Nx workspace
The spartan/stack
starts with an Nx workspace. Even better, AnalogJs comes with first-class Nx support and a preset, which will set up our favorite meta-framework for you out of the box!
Enter the following command:
npx create-nx-workspace@latest --preset=@analogjs/platform
You will be asked to choose a folder name for your workspace:
Where would you like to create your workspace?
Pick whichever fits your project the best!
Then, you will be prompted to answer the following questions:
What name would you like to use for your AnalogJs app?
Add TailwindCSS for styling?
Add tRPC for typesafe client/server interaction?
Give your application a meaningful name (we will refer to this as [YOUR_APP_NAME]
in this guide) and press y/Y
to answer both questions about adding TailwindCSS and tRPC to your application.
AnalogJs, Angular, TailwindCSS, and tRPC
With this simple command, you will have a working Nx workspace that includes an example AnalogJs application, with TailwindCSS and tRPC already set up!
Watch the video below to become more familiar with the AnalogJs setup.
Drizzle
Currently, we use an in-memory array to store the notes of our example application. Let's persist our data in an actual database. To interact with our DB, we will use the Drizzle ORM. Let's first install the necessary dependencies:
npm install drizzle-orm postgres
Dealing with postgres & CommonJs
postgres
is a CommonJs package, which directly exposes an augmented function. Therefore, we need to adjust our [YOUR_APP_NAME]/tsconfig.json
file to tell the TS compiler how to deal with it properly. Add the following line to compilerOptions
:
"allowSyntheticDefaultImports": true
Finally, we need to set up our DB connection and create a typescript schema that matches our database structure. We will add a [YOUR_APP_NAME]/src/db.ts
file with the following content:
import { drizzle } from 'drizzle-orm/postgres-js';
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
import { InferInsertModel, InferSelectModel } from 'drizzle-orm';
import postgres from 'postgres';
export const notes = pgTable('note', {
id: serial('id').primaryKey(),
note: text('note').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export type Note = InferSelectModel<typeof notes>;
export type NewNote = InferInsertModel<typeof notes>;
const client = postgres(process.env['DATABASE_URL'] ?? '');
export const db = drizzle(client);
We first declare our notes table and make Drizzle aware of all its columns and their respective types. We then declare some helper types we will use when retrieving and creating our Notes. Finally, we initialize our Postgres client and pass it to Drizzle
This is where the spartan/stack
starts to flex its muscles. We can now use this schema in our component. Go to [YOUR_APP_NAME]/src/app/pages/analog-welcome.component.ts
and replace the following line:
import { Note } from '../../note';
with:
import { Note } from '../../db';
Excellent! We are only a few steps away from end-to-end type-safety for our Angular application. We take this opportunity and delete the boilerplate file: [YOUR_APP_NAME]/src/note.ts
.
Our types now come directly from our database!
We continue and set up our backend to use Drizzle to read, create, and delete our notes. Adjust the [YOUR_APP_NAME]/src/server/trpc/routers/notes.ts
file to get the below output:
import { z } from 'zod';
import { publicProcedure, router } from '../trpc';
import { db, notes } from '../../../db';
import { eq } from 'drizzle-orm';
export const noteRouter = router({
create: publicProcedure
.input(
z.object({
note: z.string(),
})
)
.mutation(
async ({ input }) => await db.insert(notes).values({ note: input.note }).returning()
),
list: publicProcedure.query(async () => {
const selectedNotes = await db.select().from(notes);
return selectedNotes.map((note) => ({ ...note, id: +note.id }));
}),
remove: publicProcedure
.input(
z.object({
id: z.number(),
})
)
.mutation(async ({ input }) => await db.delete(notes).where(eq(notes.id, input.id)).returning()),
});
Awesome! This is all we need to persist our data. Now that we are using type-safe database interactions and also leverage Drizzle's generated schemas in your components only one thing is missing: A database!
Supabase
We will use Supabase as our database infrastructure provider. There are two ways to get up and running with Supabase:
- Connecting directly to your managed instance on supabase.com
- Locally using Docker
Option 1: Connecting to supabase.com instance
This way is super easy! Simply by creating your account, you will also have set up your first project. This means that you are ready to connect to your projects database already!
Let's connect our application to our Supabase Postgres instance: Add a .env
file at the root of your Nx workspace and add the following code snippet:
DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-SUPABASE-REFERENCE-ID].supabase.co:5432/postgres"
Make sure to add .env to your .gitignore file.
You do not want to accidentally commit your secrets to GitHub. To exclude the file from git add a new line to the .gitignore
-file and add .env
on a new line.
Option 2: Connecting to local Supabase instance
Supabase also allows you to run a version of their system locally! To get up and running you can follow this guide! They do a great job explaining how to get started and there is plenty of resources to help you if you get stuck. If you want the quick and dirty way and are on a Mac. Here is what I did to get up and running:
Install supabase CLI
brew install supabase
Log into CLI
supabase login
Create your access token from https://app.supabase.com/account/tokens and paste it into your terminal window.
Create Supabase project
# if you are in the spartan directory move UP!!!
cd ..
# create your project folder
mkdir spartan-supabase
# move into the new folder
cd spartan-supabase
# start a new git repository — important, don't skip this step
git init
Start Supabase services
supabase init
supabase start
Important: Make sure Docker is running
Make sure Docker is running and configured correctly! I had Docker already installed and running. However, my setup is not compatible with the config Supabase expects by default.
To get it to work for now I ran:
DOCKER_HOST=unix:///Users/[YOUR_USER_ACCOUNT_NAME]/.docker/run/docker.sock supabase start
The previous step can take a while as all the docker images have to be downloaded first. However, once everything completes you will see a console output that looks like this:
Started Supabase local development setup.
API URL: http://localhost:54321
DB URL: postgresql://postgres:postgres@localhost:54322/postgres
Studio URL: http://localhost:54323
Inbucket URL: http://localhost:54324
anon key: eyJh......
service_role key: eyJh......
Take your cyber-security hat off for a minute (we are working locally after all) and copy the connection string:
postgresql://postgres:postgres@localhost:54322/postgres
Add a .env
file at the root of your Nx workspace and add the connection string like so:
DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres
Perfect! You should be able to connect to your local Supabase Postgres instance now!
Setting up the Schema
While Drizzle is adding support for automatic database migrations, I like to keep them explicit and run the commands directly against the DB. Until spartan/stack comes with an automated solution like liquibase, let's manually run the following command in the SQL-Editor to create our notes table:
create sequence note_id_seq;
create table note (
id bigint not null default nextval('note_id_seq'::regclass),
note text not null,
created_at timestamp with time zone null default current_timestamp,
constraint notes_pkey primary key (id)
);
Local Development
npx nx serve [YOUR_APP_NAME]
You can now serve the local development server by running the above command.
Build for Production
npx nx build [YOUR_APP_NAME]
Finally, let's build a production bundle of our application. Run the command above. By default, AnalogJs will build a NodeJs-compatible output, which you can run with the following command:
node dist/[YOUR_APP_NAME]/analog/server/index.mjs
AnalogJs also supports multiple build presets, which makes it easy to deploy your application to most of the major cloud providers. This includes Zerops, Vercel, Cloudflare, Azure, AWS, and more.
Next Steps
Now that you know how to develop and build your application for production, you are ready to take it to the next level. Why don't you make it beautiful and accessible for everyone with spartan/ui?