Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,35 +37,37 @@ Why AdminForth:

## Project initialisation

To create an AdminForth project, run:
AdminForth supports two setup paths:

```bash
npx adminforth create-app
```
### Path 1: Existing database

During the interactive initialization process, AdminForth will ask you to provide a local database URL.
Use this path when you already have a database and your own schema or migrations. Provide your database URL with `--db`, or enter it when the CLI asks `Please specify the database URL to use`.

### Integrating AdminForth into your existing application
```bash
npx adminforth create-app --app-name myadmin --db "postgresql://user:password@localhost:5432/dbname"
cd myadmin
```

If you want to build an admin panel for an existing project that already has a database with tables, you can provide the connection URL to your existing development database, such as a local or deployed one.
When you provide your own database URL, AdminForth connects to your database but does not create Prisma schema or migrations for it. The generated project README includes the SQL or schema notes needed to add the required `adminuser` table with your own migration tool.

After that, you may want to generate AdminForth resource files from your existing database tables:
After project creation, generate AdminForth resource files from your existing tables:

```bash
npx adminforth resource
```

Resource files are needed for AdminForth to “know” about your tables and define how to work with them.
### Path 2: New database

Use the command above every time you add new tables or change their schema.
Use this path when you want AdminForth to scaffold a standalone app with a new local SQLite database. Omit `--db`, or accept the default `sqlite://.db.sqlite` value in the interactive prompt:

### Starting from scratch

If you do not have a database yet, start an empty local database, for example PostgreSQL in Docker, and provide its URL to the AdminForth CLI.

If the adminforth CLI does not detect any tables, it will suggest adding Prisma as a migration tool. Prisma is not related to AdminForth, but it is one of the most convenient migration tools.
```bash
npx adminforth create-app --app-name myadmin
cd myadmin
pnpm makemigration --name init && pnpm migrate:local
pnpm dev
```

Please follow [getting started](https://adminforth.dev/docs/tutorial/gettingStarted/).
For the new database path, the CLI can scaffold Prisma files and migration scripts for the default `sqlite://.db.sqlite` database. Please follow [getting started](https://adminforth.dev/docs/tutorial/gettingStarted/) for the full guide.

# For AdminForth developers

Expand Down
13 changes: 12 additions & 1 deletion adminforth/commands/createApp/templates/readme.md.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@ Install dependencies:
{{packageManager}} install
```

Migrate the database:
{{#if adminUserTableInstructions}}
Prepare the admin users table in your existing database before starting the app. AdminForth uses this table for back-office authentication, and your own migration tool should own this schema change:

{{{adminUserTableInstructions}}}

The generated app will seed the default `adminforth` / `adminforth` user on first start if the table is empty.
{{/if}}

{{#if prismaDbUrl}}
Create the initial migration and apply it to the database:

```bash
{{packageManagerRun}} makemigration{{packageManagerScriptArgSeparator}}--name init
{{packageManagerRun}} migrate:local
```
{{/if}}
Comment thread
NoOne7135 marked this conversation as resolved.

Start the server:

Expand Down
117 changes: 88 additions & 29 deletions adminforth/commands/createApp/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function detectAdminforthVersion() {
const adminforthVersion = detectAdminforthVersion();
const SUPPORTED_DB_URL_SCHEMES = ['sqlite://', 'postgresql://', 'mongodb://', 'mysql://', 'clickhouse://'];
const PRISMA_MIGRATION_DB_PROTOCOLS = ['sqlite', 'postgres', 'postgresql', 'mysql'];
const DEFAULT_DB_URL = 'sqlite://.db.sqlite';


export function parseArgumentsIntoOptions(rawArgs) {
Expand All @@ -57,10 +58,71 @@ export function parseArgumentsIntoOptions(rawArgs) {
return {
appName: args['--app-name'],
db: args['--db'],
dbProvided: args['--db'] !== undefined,
useNpm: args['--use-npm'],
};
}

function generateAdminUserTableInstructions(provider) {
if (provider === 'postgresql') {
return `\`\`\`sql
CREATE TABLE adminuser (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
);
\`\`\``;
}

if (provider === 'mysql') {
return `\`\`\`sql
CREATE TABLE adminuser (
id VARCHAR(191) PRIMARY KEY,
email VARCHAR(191) NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role VARCHAR(191) NOT NULL,
created_at DATETIME NOT NULL
);
\`\`\``;
}

if (provider === 'sqlite') {
return `\`\`\`sql
CREATE TABLE adminuser (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
created_at DATETIME NOT NULL
);
\`\`\``;
}

if (provider === 'clickhouse') {
return `\`\`\`sql
CREATE TABLE adminuser (
id String,
email String,
password_hash String,
role String,
created_at DateTime
)
ENGINE = MergeTree()
ORDER BY id;
\`\`\`

ClickHouse does not enforce UNIQUE constraints like PostgreSQL, MySQL, or SQLite. AdminForth authentication expects `email` values in `adminuser` to be unique, so enforce this in your ingestion/application logic and remove duplicate email rows to avoid ambiguous logins.`;
}
Comment thread
NoOne7135 marked this conversation as resolved.

if (provider === 'mongodb') {
return 'Create an `adminuser` collection with `id`, `email`, `password_hash`, `role`, and `created_at` fields. Keep `email` unique in your own schema/index setup.';
}

return null;
}

export async function promptForMissingOptions(options) {
const questions = [];

Expand All @@ -78,7 +140,7 @@ export async function promptForMissingOptions(options) {
type: 'input',
name: 'db',
message: 'Please specify the database URL to use >',
default: 'sqlite://.db.sqlite',
default: DEFAULT_DB_URL,
});
};

Expand All @@ -102,25 +164,8 @@ export async function promptForMissingOptions(options) {
db: options.db || answers.db,
useNpm: options.useNpm || answers.useNpm,
};

if (
resolvedOptions.includePrismaMigrations === undefined &&
isPrismaMigrationDbUrl(resolvedOptions.db)
) {
const prismaAnswer = await inquirer.prompt([{
type: 'select',
name: 'includePrismaMigrations',
message: 'Include Prisma migrations? >',
choices: [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
],
default: true,
}]);
resolvedOptions.includePrismaMigrations = prismaAnswer.includePrismaMigrations;
} else {
resolvedOptions.includePrismaMigrations = Boolean(resolvedOptions.includePrismaMigrations);
}
resolvedOptions.existingDb = options.dbProvided || resolvedOptions.db !== DEFAULT_DB_URL;
resolvedOptions.includePrismaMigrations = !resolvedOptions.existingDb && isPrismaMigrationDbUrl(resolvedOptions.db);

return resolvedOptions;
}
Expand Down Expand Up @@ -262,7 +307,7 @@ async function scaffoldProject(ctx, options, cwd) {
const prismaDbUrlProd = generateDbUrlForPrismaProd(connectionString);


ctx.skipPrismaSetup = !prismaDbUrl;
ctx.skipPrismaSetup = !options.includePrismaMigrations || !prismaDbUrl;
const appName = options.appName;

const filename = fileURLToPath(import.meta.url);
Expand All @@ -287,6 +332,7 @@ async function scaffoldProject(ctx, options, cwd) {
prismaDbUrlProd,
appName,
provider,
existingDb: options.existingDb,
nodeMajor: parseInt(process.versions.node.split('.')[0], 10),
sqliteFile: connectionString.protocol.startsWith('sqlite') ? connectionString.host : null,
});
Expand All @@ -310,7 +356,7 @@ function getPackageManagerTemplateData(useNpm, nodeMajor) {

async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations, options) {
const {
dbUrl, prismaDbUrl, appName, provider, nodeMajor,
dbUrl, prismaDbUrl, appName, provider, existingDb, nodeMajor,
dbUrlProd, prismaDbUrlProd, sqliteFile
} = options;
const packageManagerTemplateData = getPackageManagerTemplateData(useNpm, nodeMajor);
Expand Down Expand Up @@ -352,7 +398,14 @@ async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations,
{
src: 'readme.md.hbs',
dest: 'README.md',
data: { dbUrl, prismaDbUrl: resolvedPrismaDbUrl, appName, sqliteFile },
data: {
dbUrl,
prismaDbUrl: resolvedPrismaDbUrl,
appName,
sqliteFile,
existingDb,
adminUserTableInstructions: existingDb ? generateAdminUserTableInstructions(provider) : null,
},
},
{
src: 'AGENTS.md.hbs',
Expand Down Expand Up @@ -519,16 +572,19 @@ async function installDependenciesNpm(ctx, cwd) {

function generateFinalInstructionsPnpm(skipPrismaSetup, options) {
let instruction = '⏭️ Run the following commands to get started:\n';
if (!skipPrismaSetup)
instruction += `
instruction += `
${chalk.dim('// Go to the project directory')}
${chalk.dim('$')}${chalk.cyan(` cd ${options.appName}`)}\n`;

if (options.includePrismaMigrations && !skipPrismaSetup)
if (!skipPrismaSetup)
instruction += `
${chalk.dim('// Generate and apply initial migration')}
${chalk.dim('$')}${chalk.cyan(' pnpm makemigration --name init && pnpm migrate:local')}\n`;

if (options.existingDb)
instruction += `
${chalk.dim('// Create the adminuser table in your database using the README instructions')}\n`;

instruction += `
${chalk.dim('// Start dev server with tsx watch for hot-reloading')}
${chalk.dim('$')}${chalk.cyan(' pnpm dev')}\n
Expand All @@ -541,16 +597,19 @@ function generateFinalInstructionsPnpm(skipPrismaSetup, options) {

function generateFinalInstructionsNpm(skipPrismaSetup, options) {
let instruction = '⏭️ Run the following commands to get started:\n';
if (!skipPrismaSetup)
instruction += `
instruction += `
${chalk.dim('// Go to the project directory')}
${chalk.dim('$')}${chalk.cyan(` cd ${options.appName}`)}\n`;

if (options.includePrismaMigrations && !skipPrismaSetup)
if (!skipPrismaSetup)
instruction += `
${chalk.dim('// Generate and apply initial migration')}
${chalk.dim('$')}${chalk.cyan(' npm run makemigration -- --name init && npm run migrate:local')}\n`;

if (options.existingDb)
instruction += `
${chalk.dim('// Create the adminuser table in your database using the README instructions')}\n`;

instruction += `
${chalk.dim('// Start dev server with tsx watch for hot-reloading')}
${chalk.dim('$')}${chalk.cyan(' npm run dev')}\n
Expand Down
43 changes: 35 additions & 8 deletions adminforth/documentation/docs/tutorial/001-gettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,38 @@ nvm use 20

## Creating an AdminForth Project

The recommended way to get started with AdminForth is via the **`create-app`** CLI, which scaffolds a basic fully functional back-office application. Apart boilerplate it creates one resource for users management.
The recommended way to get started with AdminForth is via the **`create-app`** CLI, which scaffolds a basic fully functional back-office application. Apart from boilerplate, it creates one resource for users management.

You can provide options directorly:
There are two common setup paths:

### Path 1: Existing Database

Use this path when you already have a database and your own schema or migrations. Pass your database URL with `--db`, or enter it when the CLI asks `Please specify the database URL to use`:

```bash
npx adminforth create-app --app-name myadmin --db "postgresql://user:password@localhost:5432/dbname"
```

When you provide your own database URL, the CLI treats this as your own database. It does not create Prisma schema or Prisma migration scripts for that database. Instead, the generated project README contains the SQL or schema notes for adding the required `adminuser` table with your own migration tool.

After the project is created, navigate into it and generate resources from your existing tables:

```bash
cd myadmin
npx adminforth resource
```

Resource files are needed for AdminForth to know about your tables and define how to work with them. Use `npx adminforth resource` again when you add new tables or change their schema.

### Path 2: New Database

Use this path when you want AdminForth to scaffold a standalone app with a new local SQLite database. Omit `--db`, or accept the default `sqlite://.db.sqlite` value in the interactive prompt:

```bash
npx adminforth create-app --app-name myadmin --db "sqlite://.db.sqlite"
npx adminforth create-app --app-name myadmin
```

Or omit them to be prompted interactively:
Or omit all options to be prompted interactively:

```bash
npx adminforth create-app
Expand All @@ -41,6 +64,8 @@ Once the project is created, navigate into its directory:
cd myadmin # or any other name you provided
```

For the new database path, the CLI can scaffold Prisma files and migration scripts for the default SQLite database.

CLI options:

* **`--app-name`** - name for your project. Used in `package.json`, `index.ts` branding, etc. Default value: **`adminforth-app`**.
Expand Down Expand Up @@ -69,7 +94,7 @@ myadmin/
│ └── tsconfig.json # Tsconfig for Vue project (adds completion for AdminForth core components)
├── resources
│ └── adminuser.ts # Example resource file for users management
├── schema.prisma # Prisma schema file for database schema
├── schema.prisma # Prisma schema file, generated only for the new database path
├── index.ts # Main entry point: configures AdminForth & starts the server
├── package.json # Project dependencies
├── pnpm-workspace.yaml
Expand All @@ -82,15 +107,15 @@ myadmin/

### Initial Migration & Future Migrations

> ☝️ CLI creates Prisma schema file for managing migrations in relational databases, however you are not forced to use it. Instead you are free to use your favourite or existing migration tool. In this case just ignore generated prisma file, and don't run migration command which will be suggested by CLI. However you have to ensure that your migration tool will generate required table `adminuser` with same fields and types for Admin Users resource to implmenet BackOffice authentication.
For the new database path, the CLI creates Prisma files for managing migrations. Prisma is not required by AdminForth itself, but it is a convenient migration tool for standalone projects that do not have database management yet.

CLI will suggest you a command to initialize the database with Prisma:

```bash
pnpm makemigration --name init
pnpm makemigration --name init && pnpm migrate:local
```

This will create a migration file in `migrations` and apply it to the database.
This will create a migration file and apply it to the database.

In future, when you need to add new resources, you need to modify `schema.prisma` (add models, change fields, etc.). After doing any modification you need to create a new migration using next command:

Expand All @@ -100,6 +125,8 @@ pnpm makemigration --name init ; pnpm migrate:local

Other developers need to pull migration and run `pnpm migrate:local` to apply any unapplied migrations.

For the existing database path, use your own migration tool instead. The generated project README shows how to add the required `adminuser` table to your database.

## Run the Server

Now you can run your app:
Expand Down