MaveriX89
MaveriX89
Explore posts from servers
SSolidJS
Created by MaveriX89 on 4/24/2025 in #support
renderToString in Elysia BFF Layer
Random question, I have a Solid SPA with a BFF server (implemented using Elysia) -- so this is not Solid Start. This all lives under a single repository with the following root, high-level structure:
- shared (contains things shared across both Elysia BFF and Solid SPA)
- server (Elysia BFF: contains only normal TypeScript files)
- src (SolidJS SPA: contains TSX files)
- tsconfig.json (TS config for entire repo)
- vite.config.ts (Vite config for SPA)
- shared (contains things shared across both Elysia BFF and Solid SPA)
- server (Elysia BFF: contains only normal TypeScript files)
- src (SolidJS SPA: contains TSX files)
- tsconfig.json (TS config for entire repo)
- vite.config.ts (Vite config for SPA)
I am in the process of integrating an email verification feature in my Elysia server. I am sending the email using Resend like so:
// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: `<p>Click <a href="${url}">here</a> to verify your email.</p>`
});
// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: `<p>Click <a href="${url}">here</a> to verify your email.</p>`
});
The question I have is related to that html field. I would like to use a SolidJS component to layout the HTML structure of the email content leveraging the renderToString Solid API. This would add a single .tsx file in that layer. That'll convert things to the following:
// VerifyEmail.tsx (note the TSX extension)
import { renderToString } from "solid-js/web"

export const Email = (props: { url: string }) => {
return (
<html>
<body>
<h2>Onboarding</h2>
<p>
Click <a href={props.url}>here</a> to verify your email.
</p>
</body>
</html>
)
}

export const getEmailTemplate = (url: string) => renderToString(() => <Email url={url} />)

// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: getEmailTemplate(url);
});
// VerifyEmail.tsx (note the TSX extension)
import { renderToString } from "solid-js/web"

export const Email = (props: { url: string }) => {
return (
<html>
<body>
<h2>Onboarding</h2>
<p>
Click <a href={props.url}>here</a> to verify your email.
</p>
</body>
</html>
)
}

export const getEmailTemplate = (url: string) => renderToString(() => <Email url={url} />)

// sendVerificationEmail.ts
resendClient.emails.send({
from: 'Onboarding <[email protected]>',
to: user.email,
subject: 'Verify your email',
html: getEmailTemplate(url);
});
I put my component in a separate module within my Elysia BFF layer and imported the getEmailTemplate in my module that sends the email. But when I run the code, I get the following error:
ReferenceError: React is not defined
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/Email.tsx:16:71)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:559:34)
at createRoot (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/dist/server.js:58:14)
at renderToString (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:557:14)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:53:15)
at sendVerificationEmail (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:48:37)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/better-auth/dist/api/index.mjs:238:52)
ReferenceError: React is not defined
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/Email.tsx:16:71)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:559:34)
at createRoot (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/dist/server.js:58:14)
at renderToString (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/solid-js/web/dist/server.js:557:14)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:53:15)
at sendVerificationEmail (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/server/lib/auth.ts:48:37)
at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/template-better-auth-solidjs/node_modules/better-auth/dist/api/index.mjs:238:52)
This is my first time doing something like this and not sure what the proper way to do it is. A few thoughts: - Why the heck is React called out when I am using SolidJS? Feels like IDEs assume React too much 😆 - Is this the way I should be doing things? Am I allowed to have the convenience of using a component here? - If it is possible, what do I need to be aware of when it comes to bundling the server now that I have a SolidJS component living there when I didn't have any before? Appreciate any input and insights here.
47 replies
BABetter Auth
Created by MaveriX89 on 4/23/2025 in #help
Sign Up with Additional Data
I have a question about the provided sign up capabilities within Better Auth. I have a use case where I need to collect additional data about the user on sign up for my application (e.g. their address). What’s the expectation on managing that requirement with Better Auth? Do I mutate the provided User table generated by BA? Or do I create a separate database table that joins on the user table via userId? That path also comes with the implication of creating a separate API call to also write to that separate table after doing the sign up API call. Just curious what best practices are in this kind of situation.
5 replies
BABetter Auth
Created by MaveriX89 on 4/7/2025 in #help
New to SaaS Application Building
Hey all, I'm curious what people do surrounding authentication/authorization for SaaS apps. I'm new to the arena and wondering what the different solutions are. So if I intend to deploy an application across multiple tenants who have their own IdP (e.g. Azure, AWS, GCP, Logto, etc.) that I want my app to interface with, what Better Auth plugin should I be using? Organization? OIDC? SSO? A hybrid mix? If I hook up to their IdP, then I need a way to map their IdP defined roles to the app roles that my application will define. How do people typically manage that IdP configuration for the app and IdP role to app role mapping? At deploy time? Runtime check with some administrative UI pages built into the application? Sorry if these are dumb questions, but curious to get insight as I'm trying to build a scalable SaaS app.
14 replies
BABetter Auth
Created by MaveriX89 on 4/4/2025 in #help
Organization: Determine what team a user belongs to
No description
11 replies
SSolidJS
Created by MaveriX89 on 3/27/2025 in #support
How to Abstract Routers using Classes
I'm trying to create a library that leverages a subset of routing functions under the hood. With the release of TanStack Router for SolidJS, I do not want to couple the library specifically to Solid Router and want to provide a Router abstraction that consumers of the library can configure with their router of choice. The way I'm going about that is providing abstract Router classes. Below is one example:
import { type Accessor, createSignal } from "solid-js";

import type { Router } from "./types";

export class SolidRouter implements Router {
private router: typeof import("@solidjs/router") | undefined;
public loaded: Accessor<boolean>;

constructor() {
const [loaded, setLoaded] = createSignal(false);

this.loaded = loaded;

import("@solidjs/router").then((routerModule) => {
this.router = routerModule;
setLoaded(true);
});
}

pathname = () => {
if (!this.router || !this.loaded()) {
throw new Error("Router not loaded");
}

return this.router.useLocation().pathname;
};

getSearchParam = (key: string) => {
if (!this.router || !this.loaded()) {
throw new Error("Router not loaded");
}

const [searchParams] = this.router.useSearchParams();
return searchParams[key] as string | null;
};

navigate = (path: string, options?: { replace?: boolean }) => {
if (!this.router || !this.loaded()) {
throw new Error("Router not loaded");
}

const _navigate = this.router.useNavigate();
_navigate(path, options);
};
}
import { type Accessor, createSignal } from "solid-js";

import type { Router } from "./types";

export class SolidRouter implements Router {
private router: typeof import("@solidjs/router") | undefined;
public loaded: Accessor<boolean>;

constructor() {
const [loaded, setLoaded] = createSignal(false);

this.loaded = loaded;

import("@solidjs/router").then((routerModule) => {
this.router = routerModule;
setLoaded(true);
});
}

pathname = () => {
if (!this.router || !this.loaded()) {
throw new Error("Router not loaded");
}

return this.router.useLocation().pathname;
};

getSearchParam = (key: string) => {
if (!this.router || !this.loaded()) {
throw new Error("Router not loaded");
}

const [searchParams] = this.router.useSearchParams();
return searchParams[key] as string | null;
};

navigate = (path: string, options?: { replace?: boolean }) => {
if (!this.router || !this.loaded()) {
throw new Error("Router not loaded");
}

const _navigate = this.router.useNavigate();
_navigate(path, options);
};
}
The same pattern above is applied for TanStackRouter as I dynamically load the library in the constructor and implement the same interfaces with all the things that TanStack Router expects. I have two primary questions: 1. Is the above paradigm a good way of going about providing the abstraction over routing? If not, what's another way that allows users to configure routing. 2. Every time I use the class, I am getting an exception at the point of the navigate interface being called despite it being invoked while being a descendant of a <Router /> component in the JSX tree:
Uncaught (in promise) Error: <A> and 'use' router primitives can be only used inside a Route.
at invariant (utils.js?v=23f17e0f:29:15)
at useRouter (routing.js?v=23f17e0f:9:32)
at Module.useNavigate (routing.js?v=23f17e0f:42:34)
at SolidRouter.navigate (SolidRouter.tsx:42:35)
at runCallback (useLib.ts:120:14)
Uncaught (in promise) Error: <A> and 'use' router primitives can be only used inside a Route.
at invariant (utils.js?v=23f17e0f:29:15)
at useRouter (routing.js?v=23f17e0f:9:32)
at Module.useNavigate (routing.js?v=23f17e0f:42:34)
at SolidRouter.navigate (SolidRouter.tsx:42:35)
at runCallback (useLib.ts:120:14)
Not sure how to debug the error. I suppose it has something to do with invoking const _navigate = this.router.useNavigate(); in the context of a class and not a component? 🤔
1 replies
BABetter Auth
Created by MaveriX89 on 3/24/2025 in #help
How to Ensure Organization List API is authorized
I'm currently trying to use Better Auth and pair it with TanStack Query to optimize request caching as I want to dedupe multiple of the same requests that I see firing in my application. The question I have is, instead of using the client.useListOrganizations hook API that the docs recommend, I am using the raw client.organization.list API and wrapping that within the TanStack Query function like so:
export const useUserOrganizationListQuery = () => {
return createQuery(() => ({
queryKey: ["userOrganizationList"],
queryFn: async () => authClient.organization.list(),
}));
};
export const useUserOrganizationListQuery = () => {
return createQuery(() => ({
queryKey: ["userOrganizationList"],
queryFn: async () => authClient.organization.list(),
}));
};
The issue is, after signing in via email using the Better Auth client, invoking that list API call results in me receiving at 401 Not Authorized response. Can someone point to me what I'm missing to get that API to work properly after having the Better Auth cookies set properly post sign in?
2 replies
BABetter Auth
Created by MaveriX89 on 3/16/2025 in #help
Better Auth ABAC (not RBAC)
Anyone have an example of doing ABAC using Better Auth? Just curious.
1 replies
BABetter Auth
Created by MaveriX89 on 3/12/2025 in #help
Adding Members to Organization with Teams Enabled
I am using the organization plugin with teams enabled and am trying to add members to the organization/team via the addMember API call:
await auth.api.addMember({
body: {
organizationId: <org_id>,
userId: <user_id>,
role: "admin",
teamId: <team_id>, // this is not working
},
});
await auth.api.addMember({
body: {
organizationId: <org_id>,
userId: <user_id>,
role: "admin",
teamId: <team_id>, // this is not working
},
});
I am trying this but when I inspect my database table, I do not see the teamId being recorded into the database column. What am I missing?
18 replies
BABetter Auth
Created by MaveriX89 on 3/11/2025 in #help
Admin + Organization Plugin: Role Columns
I have a question about using the admin and organization plugins together. When using the admin plugin, it adds a role column to the user table and when using the organization plugin, it creates a member table that itself contains a role column along with a userId column that references the earlier user table. The question I have is, how should the relationship between those two distinct role columns be managed? What are the expectations?
54 replies
BABetter Auth
Created by MaveriX89 on 3/7/2025 in #help
Database Seed Script Using Organization Plugin
Anyone here using the organization plugin and made a local database seed script that populates your database with a fake organization and users in said organization? If so, can you share what that looks like? Step-wise sequence is: Create organization Invite users to organization Accept invitations But how does one do step 2 of the process in a seed script? Is it possible to skip it all together and directly add users to the organization using the server API?
3 replies
DTDrizzle Team
Created by MaveriX89 on 2/16/2025 in #help
Recommended Docker Compose Workflow
Hey all, I'm new to making full stack applications and am working with Drizzle to make mine. I have a question on what the best practice is when using databases that are spun up using Docker Compose and how to initialize them with Drizzle tables. I have the following docker-compose.yaml
networks:
web-app:

services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: postgres
networks:
- web-app
ports:
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

app:
build: .
depends_on:
db:
condition: service_healthy
ports:
- 8000:8000
environment:
AUTH_SECRET: <secret>
AUTH_URL: http://localhost:3000
DB_USER: postgres
DB_PASSWORD: postgres
DB_URL: postgresql://postgres:postgres@localhost:5432/postgres
networks:
- web-app
networks:
web-app:

services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: postgres
networks:
- web-app
ports:
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

app:
build: .
depends_on:
db:
condition: service_healthy
ports:
- 8000:8000
environment:
AUTH_SECRET: <secret>
AUTH_URL: http://localhost:3000
DB_USER: postgres
DB_PASSWORD: postgres
DB_URL: postgresql://postgres:postgres@localhost:5432/postgres
networks:
- web-app
I am using a code-first approach with Drizzle where I want my codebase to be the source of truth for the database since there is tight coupling between the DB and my API server + SPA. The question I have is, when my app is being spun up for a production deployment, how can I ensure that when my PostgresDB from docker spins up, it also prepped with my Drizzle tables via running drizzle-kit push when it comes alive? Essentially, I want to make sure that the definition of my database container being "ready" means, the Postgres container is up, and I pushed my Drizzle tables to it (alongside any optional seed script I decide to run -- I actually have a seed_db.ts script that I typically invoke locally using bun ./scripts/seed_db.ts which I would like to run for local dev workflows)
2 replies
SSolidJS
Created by MaveriX89 on 2/10/2025 in #support
Component in test is not updating despite signal change
I am currently writing some tests for a library I'm building with Solid. I have the following test:
const AuthStateComponent = () => {
const { user, signIn, signOut } = useSSO();

createEffect(() => {
console.log("auth state user >>>>", user());
});

return (
<Show
when={user()}
fallback={
<>
<div>Not Authenticated</div>
<button onClick={signIn}>Sign In</button>
</>
}
>
<div>Authenticated</div>
<div>{user()!.profile.name}</div>
<button onClick={signOut}>Sign Out</button>
</Show>
);
};

test("handles successful sign in", async () => {
let userManagerInstance!: UserManager;

function TestWithUserManager() {
const { userManager } = useSSO();
userManagerInstance = userManager();
return <AuthStateComponent />;
}

const signinRedirectMock = vi.fn(async () => {
await userManagerInstance.events.load(mockUser, true);
});

vi.spyOn(UserManager.prototype, "signinRedirect").mockImplementationOnce(signinRedirectMock);

const { findByText } = render(() => (
<SSOProvider authority={TEST_AUTHORITY} client_id={TEST_CLIENT_ID}>
<TestWithUserManager />
</SSOProvider>
));

expect(userManagerInstance).not.toBeUndefined();

await userEvent.click(await findByText("Sign In"));

expect(signinRedirectMock).toHaveBeenCalled();

await waitFor(async () => {
expect(await findByText("Authenticated")).toBeInTheDocument();
});
});
const AuthStateComponent = () => {
const { user, signIn, signOut } = useSSO();

createEffect(() => {
console.log("auth state user >>>>", user());
});

return (
<Show
when={user()}
fallback={
<>
<div>Not Authenticated</div>
<button onClick={signIn}>Sign In</button>
</>
}
>
<div>Authenticated</div>
<div>{user()!.profile.name}</div>
<button onClick={signOut}>Sign Out</button>
</Show>
);
};

test("handles successful sign in", async () => {
let userManagerInstance!: UserManager;

function TestWithUserManager() {
const { userManager } = useSSO();
userManagerInstance = userManager();
return <AuthStateComponent />;
}

const signinRedirectMock = vi.fn(async () => {
await userManagerInstance.events.load(mockUser, true);
});

vi.spyOn(UserManager.prototype, "signinRedirect").mockImplementationOnce(signinRedirectMock);

const { findByText } = render(() => (
<SSOProvider authority={TEST_AUTHORITY} client_id={TEST_CLIENT_ID}>
<TestWithUserManager />
</SSOProvider>
));

expect(userManagerInstance).not.toBeUndefined();

await userEvent.click(await findByText("Sign In"));

expect(signinRedirectMock).toHaveBeenCalled();

await waitFor(async () => {
expect(await findByText("Authenticated")).toBeInTheDocument();
});
});
So the context summary is, I have two primary things under test: SSOProvider and the useSSO hook. As you can see above, I have an internal test component called AuthStateComponent that is using the useSSO to either show an authenticated or unauthenticated view. The issue is, when I run my test and trigger a user update, the test component does not re-render to show the authenticated view. It always remains in the unauthenticated despite the fact the user has been updated internally within useSSO (I verified this via console logging everything). So that little createEffect log that I have within the test component only ever runs once, outputting a null, and it does not re-run after the user signal updates internally. I'm currently stuck in testing this particular area and can't proceed. Not sure exactly what I'm doing wrong here.
2 replies
SSolidJS
Created by MaveriX89 on 2/5/2025 in #support
How to filter/remove elements from an array within a Store
If I have the following createStore invocation:
export type WeeklyTimesheetSubmission = {
readonly cards: TimesheetCard[];
};

function SomeComponent() {
const [submission, setSubmission] = createStore<WeeklyTimesheetSubmission>({
cards: [],
});
}
export type WeeklyTimesheetSubmission = {
readonly cards: TimesheetCard[];
};

function SomeComponent() {
const [submission, setSubmission] = createStore<WeeklyTimesheetSubmission>({
cards: [],
});
}
What is the proper way to filter/remove an element from that cards list? I have tried the following and I get yelled at by TypeScript saying it's incorrect.
setSubmission("cards", (c) => c.id !== card.id); // Attempt 1: Fail
setSubmission("cards", (c) => c.id !== card.id, undefined!) // Attempt 2: Fail
setSubmission("cards", (c) => c.id !== card.id); // Attempt 1: Fail
setSubmission("cards", (c) => c.id !== card.id, undefined!) // Attempt 2: Fail
Since the cards field is at the top level, is the only option to reference the previous value of the store?
setSubmission((prev) => ({ ...prev, cards: prev.cards.filter((c) => c.id !== card.id) }))
setSubmission((prev) => ({ ...prev, cards: prev.cards.filter((c) => c.id !== card.id) }))
6 replies
SSolidJS
Created by MaveriX89 on 1/22/2025 in #support
Exception thrown when invoking useNavigate within Router root layout
I am having a real hard time working through this problem. I feel like I've tried everything but nothing has been working and there's probably something I'm not seeing clearly anymore. The short of it is, when I attempt to invoke navigate("/login") from my root layout component, I am greeted with the following exception:
TypeError: Cannot read properties of undefined (reading 'path')
TypeError: Cannot read properties of undefined (reading 'path')
Here is the entirety of my App.tsx file:
import "./app.css";

import { attachDevtoolsOverlay } from "@solid-devtools/overlay";

import { createAsync, Route, Router, type RouteSectionProps, useNavigate } from "@solidjs/router";
import CircleUser from "lucide-solid/icons/circle-user";
import Clock from "lucide-solid/icons/clock-4";
import LogOut from "lucide-solid/icons/log-out";
import { createEffect, ErrorBoundary, lazy, type ParentProps, Show, Suspense } from "solid-js";

import { isMedium } from "./globalSignals";
import { authClient } from "./lib/auth-client";
import { getAuthState } from "./lib/queries";
import { cn, isNil } from "./lib/utils";
import { Navbar, NavbarLink } from "./Navbar";

const Login = lazy(() => import("./pages/Login"));
const Home = lazy(() => import("./pages/Dashboard"));

attachDevtoolsOverlay();

const MainContent = (props: ParentProps) => {
return <main class="flex flex-col h-full w-full flex-grow p-4">{props.children}</main>;
};

const RootLayout = (props: RouteSectionProps<unknown>) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

return (
<div id="app-screen" class="h-screen w-screen">
<Show when={!isNil(auth())} fallback={<MainContent {...props} />}>
<div
id="root-layout"
class={cn([
"h-full",
isMedium() ? "grid grid-rows-[1fr] grid-cols-[auto_1fr]" : "flex flex-col",
])}
>
<Navbar>
<NavbarLink href="/dashboard" icon={<CircleUser />}>
Dashboard
</NavbarLink>
<NavbarLink href="/timesheets" icon={<Clock />}>
Timesheets
</NavbarLink>
<hr class="my-3" />
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate("/login");
},
},
});
}}
>
Sign Out
</NavbarLink>
</Navbar>
<MainContent {...props} />
</div>
</Show>
</div>
);
};

const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};

export const App = () => {
return (
<ErrorBoundary fallback={<div>Oops! Something went wrong!</div>}>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
</ErrorBoundary>
);
};
import "./app.css";

import { attachDevtoolsOverlay } from "@solid-devtools/overlay";

import { createAsync, Route, Router, type RouteSectionProps, useNavigate } from "@solidjs/router";
import CircleUser from "lucide-solid/icons/circle-user";
import Clock from "lucide-solid/icons/clock-4";
import LogOut from "lucide-solid/icons/log-out";
import { createEffect, ErrorBoundary, lazy, type ParentProps, Show, Suspense } from "solid-js";

import { isMedium } from "./globalSignals";
import { authClient } from "./lib/auth-client";
import { getAuthState } from "./lib/queries";
import { cn, isNil } from "./lib/utils";
import { Navbar, NavbarLink } from "./Navbar";

const Login = lazy(() => import("./pages/Login"));
const Home = lazy(() => import("./pages/Dashboard"));

attachDevtoolsOverlay();

const MainContent = (props: ParentProps) => {
return <main class="flex flex-col h-full w-full flex-grow p-4">{props.children}</main>;
};

const RootLayout = (props: RouteSectionProps<unknown>) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

return (
<div id="app-screen" class="h-screen w-screen">
<Show when={!isNil(auth())} fallback={<MainContent {...props} />}>
<div
id="root-layout"
class={cn([
"h-full",
isMedium() ? "grid grid-rows-[1fr] grid-cols-[auto_1fr]" : "flex flex-col",
])}
>
<Navbar>
<NavbarLink href="/dashboard" icon={<CircleUser />}>
Dashboard
</NavbarLink>
<NavbarLink href="/timesheets" icon={<Clock />}>
Timesheets
</NavbarLink>
<hr class="my-3" />
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate("/login");
},
},
});
}}
>
Sign Out
</NavbarLink>
</Navbar>
<MainContent {...props} />
</div>
</Show>
</div>
);
};

const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};

export const App = () => {
return (
<ErrorBoundary fallback={<div>Oops! Something went wrong!</div>}>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
</ErrorBoundary>
);
};
The area to focus on is the onClick for the Sign Out NavbarLink. In the callback, I am invoking the Better Auth client to sign out and on success, I navigate to /login. It's that invocation that causes the exception. I'm not exactly sure the proper way to debug this and could use all the help I can get
33 replies
BABetter Auth
Created by MaveriX89 on 1/21/2025 in #help
Better Auth + Solid Start
When using Better Auth within Solid Start -- after doing all the required configuration, do we simply use the Better Auth server client (instead of the client one) to invoke all auth related matters? So given a Solid Start action like below:
export const logIn = action((formData: FormData) => {
"use server"
const username = String(formData.get("username"));
const password = String(formData.get("password"));

let error = validateUsername(username) || validatePassword(password);

if (error) {
return new Error(error);
}

try {
// Better Auth Server client
const response = await auth.api.signInUsername({ body: { username, password } });
// other stuff..
} catch (err) {
return err as Error;
}

throw redirect("/");
}, "logIn");
export const logIn = action((formData: FormData) => {
"use server"
const username = String(formData.get("username"));
const password = String(formData.get("password"));

let error = validateUsername(username) || validatePassword(password);

if (error) {
return new Error(error);
}

try {
// Better Auth Server client
const response = await auth.api.signInUsername({ body: { username, password } });
// other stuff..
} catch (err) {
return err as Error;
}

throw redirect("/");
}, "logIn");
Is the above the expected way we interface with Better Auth on the server? Or is there some other behind the scenes stuff happening due to the below configuration?
// routes/api/*auth.ts
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";

export const { GET, POST } = toSolidStartHandler(auth);
// routes/api/*auth.ts
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";

export const { GET, POST } = toSolidStartHandler(auth);
Or lastly, should we still be using the Better Auth client on the frontend and do:
const response = await authClient.signIn.username(credentials));
const response = await authClient.signIn.username(credentials));
3 replies
SSolidJS
Created by MaveriX89 on 1/21/2025 in #support
SolidJS SPA with Better Auth
I am trying to build a SolidJS SPA that uses the new Better Auth library to manage all things related to authentication/authorization for my app. Post all the needed configuration for it, on the SPA side, I have to use the Better Auth client to invoke all the auth related methods on my UI. I have one example here that I'm wrestling with in trying to get a redirect to happen after the user signs in but the redirect is not happening on the Solid SPA side: My Better Auth Client configuration:
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
My SignIn component:
import { Button } from "@kobalte/core/button";
import { action, query, redirect } from "@solidjs/router";
import { createEffect } from "solid-js";

import Logo from "../assets/logo_login.svg";
import { TextInput } from "../components/TextInput";
import { authClient } from "../lib/auth-client";
import { convertToRecord } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

export const getUserSession = query(() => authClient.getSession(), "userSession");

const signIn = action(async (data: FormData) => {
authClient.signIn.username(convertToRecord<SignInForm>(data), {
onSuccess: () => {
console.log("ON SUCCESS >>>>") // this logs correctly
throw redirect("/home", { revalidate: getUserSession.key }); // <--- this does not redirect
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
});

export default function SignIn() {
return (
<div class="h-full flex flex-col items-center justify-center gap-2">
<img src={Logo} width="200" />
<form action={signIn} method="post" class="flex flex-col gap-4">
<TextInput name="username" label="Username" />
<TextInput name="password" label="Password" type="password" />
<Button class="button" type="submit">
Login
</Button>
</form>
</div>
);
}
import { Button } from "@kobalte/core/button";
import { action, query, redirect } from "@solidjs/router";
import { createEffect } from "solid-js";

import Logo from "../assets/logo_login.svg";
import { TextInput } from "../components/TextInput";
import { authClient } from "../lib/auth-client";
import { convertToRecord } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

export const getUserSession = query(() => authClient.getSession(), "userSession");

const signIn = action(async (data: FormData) => {
authClient.signIn.username(convertToRecord<SignInForm>(data), {
onSuccess: () => {
console.log("ON SUCCESS >>>>") // this logs correctly
throw redirect("/home", { revalidate: getUserSession.key }); // <--- this does not redirect
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
});

export default function SignIn() {
return (
<div class="h-full flex flex-col items-center justify-center gap-2">
<img src={Logo} width="200" />
<form action={signIn} method="post" class="flex flex-col gap-4">
<TextInput name="username" label="Username" />
<TextInput name="password" label="Password" type="password" />
<Button class="button" type="submit">
Login
</Button>
</form>
</div>
);
}
I'm suspecting that perhaps, the issue has to do with the fact that I am doing the throw redirect inside of the onSuccess handler of the Better Auth client as opposed to the scope of the action itself? Wondering if others have attempted this yet.
5 replies
SSolidJS
Created by MaveriX89 on 1/21/2025 in #support
TanStack Query vs Solid Router
I'm looking to use Solidjs for a production app and have a question about a library I typically reach for (TanStack Query) for my SPAs and some of the built-in APIs for Solid Router: query + createAsync. Is there any reason to use TanStack when Solid Router comes with those built-in? Just wondering if there is something I'm missing because, the query + createAsync combination if I'm not mistaken provides a lot of the same benefits that TanStack does with request de-deduping and caching by key. Wondering if there are other gaps present that I am not privy to (e.g. easy request refetching/polling on interval or conditional fetching like the enabled field from TanStack Query). For the visually inclined -- the question is what is the recommendation between these two options and the pros/cons of each: Option 1:
import { createQuery } from "@tanstack/solid-query";

function SomeComponent() {
const query = createQuery(() => ({
queryKey: ["userSession"],
queryFn: async () => {
const response = await fetch(...);
if (!response.ok) throw new Error("Failed to fetch data");
return response.json()
},
enabled: true,
refetchInterval: 5000,
}));
}
import { createQuery } from "@tanstack/solid-query";

function SomeComponent() {
const query = createQuery(() => ({
queryKey: ["userSession"],
queryFn: async () => {
const response = await fetch(...);
if (!response.ok) throw new Error("Failed to fetch data");
return response.json()
},
enabled: true,
refetchInterval: 5000,
}));
}
VS Option 2:
import { createAsync, query } from "@solidjs/router";
const getUserSession = query(() => fetch(...), "userSession");

function SomeComponent() {
const session = createAsync(() => getUserSession());
// do stuff ...
}
import { createAsync, query } from "@solidjs/router";
const getUserSession = query(() => fetch(...), "userSession");

function SomeComponent() {
const session = createAsync(() => getUserSession());
// do stuff ...
}
P.S. Just throwing it out there but, man, Option 2 look really good haha
17 replies
BABetter Auth
Created by MaveriX89 on 1/21/2025 in #help
Getting UNPROCESSABLE_ENTITY when attempting to sign up a new user
Hitting another issue with Better Auth where I get an error when attempting to do the following:
authClient.signUp.email({
name: "Test User",
password: "password1234",
username: "test"
})
authClient.signUp.email({
name: "Test User",
password: "password1234",
username: "test"
})
I receive this error:
BetterCallAPIError: API Error: UNPROCESSABLE_ENTITY Failed to create user
cause: {
"message": "Failed to create user",
"details": {
"length": 164,
"name": "error",
"severity": "ERROR",
"code": "22P02",
"where": "unnamed portal parameter $1 = '...'",
"originalLine": 156,
"originalColumn": 12,
"file": "uuid.c",
"routine": "string_to_uuid",
"stack": "error: invalid input syntax for type uuid: \"fc0vhdcGvNCF6VDIqXCvfSAxuLCAMxJc\"\n at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/time-tracker/node_modules/pg-pool/index.js:45:11)\n at processTicksAndRejections (native:7:39)"
},
"code": "FAILED_TO_CREATE_USER"
}
BetterCallAPIError: API Error: UNPROCESSABLE_ENTITY Failed to create user
cause: {
"message": "Failed to create user",
"details": {
"length": 164,
"name": "error",
"severity": "ERROR",
"code": "22P02",
"where": "unnamed portal parameter $1 = '...'",
"originalLine": 156,
"originalColumn": 12,
"file": "uuid.c",
"routine": "string_to_uuid",
"stack": "error: invalid input syntax for type uuid: \"fc0vhdcGvNCF6VDIqXCvfSAxuLCAMxJc\"\n at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/time-tracker/node_modules/pg-pool/index.js:45:11)\n at processTicksAndRejections (native:7:39)"
},
"code": "FAILED_TO_CREATE_USER"
}
I'm wondering if this is because Better Auth does not like the fact that I modified the primary keys of the default Drizzle tables it generated for me to use uuid instead of integer?
import { pgSchema, uuid } from "drizzle-orm/pg-core";

export const authSchema = pgSchema("auth");

export const UserTable = authSchema.table("user", {
id: uuid("id").defaultRandom().primaryKey(), // <-- did this to all the default tables
});
import { pgSchema, uuid } from "drizzle-orm/pg-core";

export const authSchema = pgSchema("auth");

export const UserTable = authSchema.table("user", {
id: uuid("id").defaultRandom().primaryKey(), // <-- did this to all the default tables
});
Appreciate any and all help with this
3 replies