Primeiros Passos

Publicado em

08/03/2025

Primeiros Passos com o SaaS Boilerplate

Agora que você já configurou seu ambiente de desenvolvimento, é hora de começar a construir sua aplicação SaaS. Este guia vai te orientar pelos primeiros passos essenciais para se familiarizar com o SaaS Boilerplate e criar seu primeiro recurso.

Entendendo a Arquitetura

Antes de mergulhar no código, é importante entender a arquitetura do SaaS Boilerplate. O projeto segue uma arquitetura baseada em recursos com clara separação de responsabilidades, impulsionada pelo Igniter.js para desenvolvimento de API com segurança de tipos.

Diretórios Principais

  • src/saas-boilerplate/: Contém recursos principais do SaaS como autenticação, faturamento e organizações
  • src/app/: Páginas do Next.js app router organizadas por seção (dashboard, marketing, auth)
  • src/content/: Conteúdo Markdown e MDX para documentação, posts de blog, etc.
  • src/core/: Componentes principais da aplicação, provedores e utilitários
  • src/features/: Aqui é onde ficarão os recursos específicos da sua aplicação

Explorando o Boilerplate

Começe explorando os recursos e páginas existentes para entender como estão implementados. Aqui estão algumas áreas importantes para verificar:

  1. Fluxo de Autenticação: Navegue até /src/saas-boilerplate/features/auth para ver como a autenticação está implementada
  2. Páginas do Dashboard: Verifique o diretório /src/app/(dashboard) para ver a interface do dashboard
  3. Páginas de Marketing: Explore /src/app/(marketing) para ver a página inicial e outros conteúdos de marketing
  4. Rotas da API: Examine os controladores em vários módulos de recursos para entender a estrutura da API

Criando Seu Primeiro Recurso

Vamos ver como criar seu primeiro recurso usando o Igniter.js. Vamos criar um recurso simples de "Notas" como exemplo, seguindo nossa arquitetura multi-tenant onde as organizações são os principais inquilinos.

Passo 1: Definir o Esquema do Banco de Dados

Primeiro, defina o modelo de dados do seu recurso no esquema Prisma. Observe que relacionamos entidades a organizações, não a usuários:

// prisma/schema.prisma
model Note {
  id             String       @id @default(cuid())
  title          String
  content        String
  organization   Organization @relation(fields: [organizationId], references: [id])
  organizationId String
  createdAt      DateTime     @default(now())
  updatedAt      DateTime     @updatedAt
}

Execute a migração para atualizar seu banco de dados:

bun db:migrate:dev --name add_notes

Passo 2: Gerar o Recurso

Usando a CLI do Igniter.js, gere um novo esqueleto de recurso:

bun igniter generate feature

Selecione "Notes" das opções disponíveis com base no seu esquema Prisma. Isso criará:

  • Tipos e interfaces
  • Controladores que lidam com requisições HTTP
  • Procedimentos que contêm lógica de negócios
  • Rotas de API com permissões adequadas

Passo 3: Entendendo os Arquivos Gerados

Vamos olhar os componentes principais:

Tipos (notes.interfaces.ts)

export interface Note {
  id: string
  title: string
  content: string
  organizationId: string
  organization?: Organization
  createdAt: Date
  updatedAt: Date
}

export interface CreateNoteDTO {
  title: string
  content: string
  organizationId: string
}

export interface NoteQueryParams {
  page?: number
  limit?: number
  sortBy?: string
  sortOrder?: 'asc' | 'desc'
  search?: string
  organizationId: string
}

Controlador com Permissões (notes.controller.ts)

export const NotesController = igniter.controller({
  name: 'notes',
  path: '/notes',
  actions: {
    findMany: igniter.query({
      method: 'GET',
      path: '/',
      use: [NotesFeatureProcedure(), AuthFeatureProcedure()],
      handler: async ({ response, request, context }) => {
        const auth = await context.auth.getSession({
          requirements: 'authenticated',
          roles: ['admin', 'owner', 'member']
        })
        
        const result = await context.notes.findMany({
          ...request.query,
          organizationId: auth.organization.id
        })
        
        return response.success(result)
      }
    })
  }
})

Procedimento (notes.procedure.ts)

export const NotesFeatureProcedure = igniter.procedure({
  name: 'NotesFeatureProcedure',
  handler: async (_, { context }) => {
    return {
      notes: {
        findMany: async (query: NoteQueryParams): Promise<Note[]> => {
          const result = await context.providers.database.note.findMany({
            where: query.search ? {
              OR: [
                { title: { contains: query.search } },
                { content: { contains: query.search } }
              ],
              organizationId: query.organizationId
            } : {
              organizationId: query.organizationId
            },
            skip: query.page ? (query.page - 1) * (query.limit || 10) : undefined,
            take: query.limit,
            orderBy: query.sortBy ? { [query.sortBy]: query.sortOrder || 'asc' } : undefined
          })

          return result as Note[]
        }
      }
    }
  }
})

Passo 4: Entendendo a Arquitetura

  1. Design Multi-tenant: Todos os recursos são limitados a organizações, não a usuários individuais
  2. Sistema de Permissões:
    • Use AuthFeatureProcedure() para lidar com autenticação
    • Defina papéis necessários (admin, owner, member)
    • Acesse o contexto da organização via auth.organization.id
  3. Separação de Responsabilidades:
    • Controladores: Lidam com requisições HTTP e permissões
    • Procedimentos: Contêm lógica de negócios e interagem com provedores
    • Provedores: Lidam com operações de banco de dados e serviços externos

Passo 5: Registrar o Recurso

Registre seu recurso no roteador:

// src/features/notes/notes.router.ts
import { NotesController } from './controllers/notes.controller'

export const notesRouter = {
  controllers: [NotesController]
}

Next Steps

  1. Explore existing features like lead to understand best practices
  2. Implement client-side components using the generated API
  3. Add custom business logic in procedures
  4. Extend the permission system for your specific needs

Using Your Feature

Let's look at some examples of how to use your feature in different contexts:

Client-Side Form Example

// src/features/notes/presentation/components/note-upsert-sheet.tsx
'use client'

import { z } from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { api } from '@/igniter.client'

const schema = z.object({
  title: z.string().min(1, 'Title is required'),
  content: z.string().min(1, 'Content is required')
})

export function NoteUpsertSheet() {
  const form = useForm({
    resolver: zodResolver(schema)
  })

  const { mutate: createNote } = api.notes.create.useMutation({
    onSuccess: () => {
      form.reset()
    }
  })

  const onSubmit = form.handleSubmit((data) => {
    createNote(data)
  })

  return (
    <form onSubmit={onSubmit}>
      <input {...form.register('title')} />
      <textarea {...form.register('content')} />
      <button type="submit">Create Note</button>
    </form>
  )
}

Client-Side Query Example

// src/app/(dashboard)/app/organizations/page.tsx
'use client'

import { api } from '@/igniter.client'

export default function OrganizationSwitcher() {
  const { data: organizations } = api.organization.findMany.useQuery()

  return (
    <div>
      {organizations?.map((org) => (
        <div key={org.id}>
          {org.name}
        </div>
      ))}
    </div>
  )
}

Server-Side Query Example

// src/app/(dashboard)/app/leads/page.tsx
import { api } from '@/igniter.server'

export default async function LeadsPage() {
  const contacts = await api.lead.findMany.query()

  return (
    <LeadDataTableProvider initialData={contacts.data ?? []}>
      <PageWrapper>
        <PageHeader>
          <PageTitle>Leads</PageTitle>
        </PageHeader>
      </PageWrapper>
    </LeadDataTableProvider>
  )
}

Check out other guides to learn more about:

  • Role-based access control
  • Organization management
  • Custom providers and services
SaaS Boilerplate

Acelere seu desenvolvimento

Construa aplicações SaaS completas em minutos com nosso boilerplate moderno. Autenticação, pagamentos, gerenciamento de usuários e muito mais!

Conheça o SaaS Boilerplate

Você também pode gostar