#Create form action

39 messages · Page 1 of 1 (latest)

sonic portal
#

I have a astro db with this schema:

columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
price: column.number(),
rent: column.number(),
rentToPriceRatio: column.number(),
surfaceArea: column.number(),
pricePerSquareMeter: column.number(),
yearBuilt: column.number(),
energyLabel: column.text(),
expenses: column.number(),
maintenance: column.number(),
cityTaxes: column.number(),
insurance: column.number(),
financeRatio: column.number(),
interest: column.number(),
duration: column.number(),
capitalGainTax: column.number(),
yearlyExpenses: column.number(),
yearlyCashFlow: column.number(),
bar: column.number(),
index: column.number(),
bidPrice: column.number(),
},

I created a form, and I want to create an action that inserts the form values into the DB.

stray flameBOT
#

Hi @sonic portal. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You can model your action on the addComment example in the Astro DB docs, but adapt the schema fields and Zod types to your table. [Astro DB insert]

Assuming your table is called Property, an action could look like this:

// src/actions/index.ts
import { db, Property } from 'astro:db';
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  addProperty: defineAction({
    input: z.object({
      name: z.string(),
      price: z.number(),
      rent: z.number(),
      rentToPriceRatio: z.number(),
      surfaceArea: z.number(),
      pricePerSquareMeter: z.number(),
      yearBuilt: z.number(),
      energyLabel: z.string(),
      expenses: z.number(),
      maintenance: z.number(),
      cityTaxes: z.number(),
      insurance: z.number(),
      financeRatio: z.number(),
      interest: z.number(),
      duration: z.number(),
      capitalGainTax: z.number(),
      yearlyExpenses: z.number(),
      yearlyCashFlow: z.number(),
      bar: z.number(),
      index: z.number(),
      bidPrice: z.number(),
    }),
    handler: async (input) => {
      const inserted = await db
        .insert(Property)
        .values(input)
        .returning(); // returns the inserted row(s)
      return inserted;
    },
  }),
};

This follows the same pattern as the Comment example: use defineAction, validate inputs with Zod, then call db.insert(YourTable).values(input).returning(). [Astro DB insert]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame What if I don't have each property in the form, but want to calculcate them based on what was entered ?

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You can compute additional fields inside the action’s handler before calling db.insert(). The docs show that handler receives the validated input object and you pass that object (or a modified version of it) into .values(). [Astro DB insert]

Conceptually:

import { db, Property } from 'astro:db';
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  addProperty: defineAction({
    input: z.object({
      // only fields actually submitted by the form:
      name: z.string(),
      price: z.number(),
      rent: z.number(),
      surfaceArea: z.number(),
      // etc...
    }),
    handler: async (input) => {
      // derive the missing fields from input
      const rentToPriceRatio = input.rent / input.price;
      const pricePerSquareMeter = input.price / input.surfaceArea;

      // build the full row object for your table
      const row = {
        ...input,
        rentToPriceRatio,
        pricePerSquareMeter,
        // set other derived / defaulted fields as needed
      };

      const inserted = await db
        .insert(Property)
        .values(row)
        .returning();

      return inserted;
    },
  }),
};

Key idea: your Zod input schema only needs to describe what the form sends; computed fields are added in the handler before inserting. The docs don’t give a derived-field example explicitly, but this pattern is directly compatible with their inputhandlerdb.insert(...).values(input) flow. [Astro DB insert]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame how do I submit my form to this action?

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To submit your form to that server action, use an HTML <form> with:

  • method="POST"
  • action={actions.addProperty} (or whatever you named the action)
  • The page must be on‑demand rendered (no prerender). [Call from form]

Example:

---
// src/pages/properties/create.astro
import { actions } from 'astro:actions';
---

<form method="POST" action={actions.addProperty}>
  <label>
    Name
    <input type="text" name="name" required />
  </label>

  <label>
    Price
    <input type="number" name="price" required />
  </label>

  <label>
    Rent
    <input type="number" name="rent" required />
  </label>

  <label>
    Surface area
    <input type="number" name="surfaceArea" required />
  </label>

  <button type="submit">Save property</button>
</form>

On the server page/component where you want to react to the submission, you can read the result with Astro.getActionResult(actions.addProperty) (optional, for redirects or showing errors). [Call from form]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame I created a [properties].astro file

if the name of a property is 'test 1' I want to be able to go to site.com/test-1 and see the data. Create the contents of the file

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You’ll need a dynamic route that:

  1. Reads the slug from the URL ([slug].astro).
  2. Looks up the matching row in Astro DB by that slug (you can derive/store a slug when inserting).
  3. Renders the property data.

The docs show querying from an Astro page with db.select().from(Table) and a where condition, reusing Drizzle helpers like eq. [Astro DB query]

Assuming:

  • Your table is Property.
  • You’ve stored a slug column like test-1 for the “test 1” property (the docs do not show slug generation, so that part is up to you).

Then src/pages/[slug].astro can look like:

---
import { db, Property, eq } from 'astro:db';

const { slug } = Astro.params;

// Fetch the property whose slug matches the URL segment
const [property] = await db
  .select()
  .from(Property)
  .where(eq(Property.slug, slug));

// Optionally, handle "not found"
if (!property) {
  return new Response('Not found', { status: 404 });
}
---

<html lang="en">
  <head>
    <title>{property.name}</title>
  </head>
  <body>
    <h1>{property.name}</h1>
#

<p>Price: {property.price}</p>
<p>Rent: {property.rent}</p>
<p>Rent / Price: {property.rentToPriceRatio}</p>
<p>Surface area: {property.surfaceArea}</p>
<p>Price / m²: {property.pricePerSquareMeter}</p>
<p>Year built: {property.yearBuilt}</p>
<p>Energy label: {property.energyLabel}</p>
<p>Expenses: {property.expenses}</p>
<p>Maintenance: {property.maintenance}</p>
<p>City taxes: {property.cityTaxes}</p>
<p>Insurance: {property.insurance}</p>
<p>Finance ratio: {property.financeRatio}</p>
<p>Interest: {property.interest}</p>
<p>Duration: {property.duration}</p>
<p>Capital gain tax: {property.capitalGainTax}</p>
<p>Yearly expenses: {property.yearlyExpenses}</p>
<p>Yearly cash flow: {property.yearlyCashFlow}</p>
<p>Bar: {property.bar}</p>
<p>Index: {property.index}</p>
<p>Bid price: {property.bidPrice}</p>
</body>
</html>


Notes:

- The pattern `const rows = await db.select().from(Comment);` and using `eq()` in `where()` mirrors the examples in the docs, just applied to your `Property` table. [[Astro DB query](<https://web.archive.org/web/20241007021202/https://docs.astro.build/en/guides/astro-db/#query-your-database>)]
- Because the docs don’t cover slug creation, you must ensure a `slug` field exists and is filled (e.g., `test-1`) when inserting properties so that `/test-1` resolves correctly.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame I use this, but I get no content. Just a 'Not found' page.


export const prerender = false;
import Layout from "@/layouts/Layout.astro";
import { db, Properties as prop, eq } from "astro:db";
import slugify from "slugify";

const { name } = Astro.params;

if (!name) {
return new Response("Not found", { status: 404 });
}

const slug = slugify(name, { lower: true, strict: true });

// Fetch the property whose slug matches the URL segment
const property = await db.select().from(prop).where(eq(prop.name, slug));

// Optionally, handle "not found"
if (!property) {
return new Response("Not found", { status: 404 });
}

<Layout>
<h1>{JSON.stringify(property)}</h1>
</Layout>

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Two issues in your code:

  1. You’re slugifying the URL param but querying by name (not slug)
  2. You’re not checking the result shape from db.select() correctly

From the Astro DB “Select” example, db.select().from(Comment) returns an array of rows, not a single row. [Astro DB query]

const comments = await db.select().from(Comment);

So:

const property = await db.select().from(prop).where(eq(prop.name, slug));
if (!property) { /* ... */ }

will never be null/undefined – it will be [] when no rows match.

Also, you’re doing:

const slug = slugify(name, { lower: true, strict: true });
const property = await db.select().from(prop).where(eq(prop.name, slug));

This will only work if the stored name value is already slugified (e.g. "test-1"). If name in the DB is "test 1", this filter will never match.

Given the docs only show filtering by actual column values, not slug derivation, the simplest working version is:

  • Use the URL param directly as the DB value you query.
  • Or query by a dedicated slug column that you store in the DB.

Option 1 – URL param matches name exactly

---
export const prerender = false;
import Layout from '@/layouts/Layout.astro';
import { db, Properties as prop, eq } from 'astro:db';

const { name } = Astro.params;

if (!name) {
  return new Response('Not found', { status: 404 });
}

const rows = await db
  .select()
  .from(prop)
  .where(eq(prop.name, name)); // no slugify

const property = rows[0];

if (!property) {
  return new Response('Not found', { status: 404 });
}
---

<Layout>
  <h1>{property.name}</h1>
  <pre>{JSON.stringify(property, null, 2)}</pre>
</Layout>

Option 2 – Use a slug column

#

If your table has a slug column and you store "test-1" there, use:

---
export const prerender = false;
import Layout from '@/layouts/Layout.astro';
import { db, Properties as prop, eq } from 'astro:db';

const { name: slug } = Astro.params;

if (!slug) {
  return new Response('Not found', { status: 404 });
}

const rows = await db
  .select()
  .from(prop)
  .where(eq(prop.slug, slug)); // query by slug column

const property = rows[0];

if (!property) {
  return new Response('Not found', { status: 404 });
}
---

<Layout>
  <h1>{property.name}</h1>
  <pre>{JSON.stringify(property, null, 2)}</pre>
</Layout>

Both patterns follow the documented select().from(Table) API and handling of the returned array. [Astro DB query]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame is it possible to host a SSR site on a free hosting like netlify, and then have a form that allows a file upload which then gets saved on the server?

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Astro’s docs say you can deploy an SSR Astro site to Netlify using the Netlify adapter. [Deploy to Netlify]

However, the provided docs do not describe:

  • Handling file uploads in actions or API routes.
  • Writing uploaded files to a persistent filesystem on Netlify (or any other host).

Because of that, I can’t confidently say (from these sources) that you can upload a file from a form and then save it on the server’s filesystem when using SSR on Netlify’s free tier. The guides only cover HTML/SSR configuration and deployment, not upload/storage behavior. [Deploy to Netlify]

So:

  • SSR on Netlify with Astro: clearly supported via @astrojs/netlify and output: 'server'. [Deploy to Netlify]
  • File upload + saving files on the server: not covered in the available Astro docs, so I can’t confirm whether that is possible or recommended on Netlify’s free hosting from these sources alone.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame Which auth framework should I use? I want something that's easy to setup. It can use Astro DB/Turso if needed since I also use that. I want something lightweight and simple as I'm the only user.

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

For something lightweight and simple, the Astro docs highlight two main auth options that fit well with Astro (and with a DB like Astro DB/Turso):

1. Lucia (simple, framework‑agnostic, good Astro support)

Astro’s Authentication guide calls out Lucia as a “framework‑agnostic, session-based authentication library with great Astro support.” [Authentication guide]

Setup in Astro is:

  • Install:

    npm install lucia
    

    [Authentication guide]

  • Follow Lucia’s own “Getting started in Astro” guide to wire it to your database (you’d pick an adapter compatible with your DB) and create users/sessions tables. [Authentication guide]

They also link full Astro tutorials for:

  • Username/password auth
  • GitHub OAuth

with example projects you can copy. [Authentication guide]

Why it matches your needs:

  • Designed to be simple and framework‑agnostic.
  • Session‑based and easy to hook to a relational DB (including one you host on Turso).
  • Good fit for a single‑user / low‑complexity setup.

2. Auth.js via auth-astro (if you prefer provider-based OAuth)

Astro also documents Auth.js using the community adapter auth-astro. [Auth.js section]

Basic setup:

  • Add the integration:

    npx astro add auth-astro
    
#
  • Create auth.config.ts with providers, e.g. GitHub:

    import GitHub from '@auth/core/providers/github';
    import { defineConfig } from 'auth-astro';
    
    export default defineConfig({
      providers: [
        GitHub({
          clientId: import.meta.env.GITHUB_CLIENT_ID,
          clientSecret: import.meta.env.GITHUB_CLIENT_SECRET,
        }),
      ],
    });
    

    [Auth.js section]

  • Then use auth-astro/client for signIn / signOut and auth-astro/server’s getSession in your pages. [Auth.js section]

When to pick this:

  • You mainly want to log in with GitHub/Google/etc.
  • You don’t mind configuring OAuth providers and environment variables.

Recommendation for your case

Given:

  • You want lightweight and simple.
  • You’re the only user.
  • You are fine using a DB like Astro DB/Turso.

From the docs, Lucia is the better match:

  • Explicitly called out as having great Astro support and being framework‑agnostic. [Authentication guide]
  • Good examples for Astro with username/password, which is all you really need as a single user.

So: start with Lucia and follow its “Getting started in Astro” plus the username/password tutorial linked from the Astro authentication guide. [Authentication guide]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame how do I avoid server island urls from showing up in my Google Search Console reports?

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The knowledge sources only explain what server islands are and how server:defer works; they do not mention search engines, indexing, or Google Search Console, so I can’t give a documented, Astro‑specific way to hide server‑island URLs from GSC.

From the docs we know:

  • Each component marked with server:defer is split into its own special route that a small script fetches at runtime. [Server islands guide]
  • These routes are meant to be internal endpoints used by the client script, not part of your normal page structure. [Server islands guide]

However, the docs do not say:

  • What those internal URLs look like.
  • How to configure them.
  • How to exclude them from indexing or from Google Search Console reports.

Because this isn’t covered, I can’t reliably tell you, based on the official docs, how to prevent those URLs from appearing in GSC (e.g. via robots rules, headers, or config).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sonic portal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sonic portal
#

@stray flame I added translations to my site like in the Astro Docs & Recipes.

I have 4 languages: french (default), dutch, english and german. I server customers from Belgium (main country), France, The Netherlands, and Germany.

How can I auto-redirect people from those countries to the correct url? My main domain is domain.com, and then I have /nl, /en and /de

stray flameBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The knowledge sources only describe browser language detection, not country-based geolocation or automatic redirects by country. So I can’t give an Astro‑specific, country‑aware redirect solution from these docs alone.

What is documented:

  • With i18n routing enabled, Astro exposes:
    • Astro.preferredLocale – the best match between the browser’s Accept-Language header and your configured i18n.locales.
    • Astro.preferredLocaleList – all matching locales between browser and your site.
    • Astro.currentLocale – the locale from the current URL (or i18n.defaultLocale if none). [i18n browser detection]

These are based on language, not country. The docs do not mention:

  • Detecting the visitor’s country (e.g. Belgium vs France).
  • Redirecting by country.
  • Mapping countries to locales.

Given that limitation, the closest you can get with the documented features is:

  1. Configure your i18n.locales for fr, nl, en, de and set fr as i18n.defaultLocale. [i18n browser detection]
  2. On your root route (/), in an on‑demand rendered page (SSR/hybrid), read Astro.preferredLocale.
  3. If Astro.currentLocale is the default (e.g. you’re on /) and Astro.preferredLocale is one of nl, en, or de, perform a redirect to /<preferredLocale>/....

Conceptually (not full code, since the docs don’t show a redirect example here):

#

But: this will be language-based, not country-based (e.g. a French user in Belgium and a French user in France both look like fr).

So, based on the available docs:

  • You can auto‑route by browser language using Astro.preferredLocale.
  • There is no documented way in Astro’s i18n guide to auto‑redirect by country (Belgium vs France vs Netherlands vs Germany).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: