0.1.24 • Published 4 months ago

create-nickjs v0.1.24

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

Nick.js

Nick.js is a lightweight web framework (glorified starter project, really) built with Bun, ElysiaJS, Kysely, React, and Tailwind. It is designed to be type-safe, performant, easy to use, and flexible.

It is is very beta software, mostly intended for personal use. Exercise caution if using for any serious project.

Installation

To create a new Nick.js project:

bun create nickjs

Be sure to verify that you get the latest version of Nick.js (i.e. the version in package.json). There is currently a Bun bug that results in bunx using stale versions of packages.

SSR

Nick.js supports basic server-side rendering + hydration. Each incoming page request is routed to a controller (i.e. an async function), which is responsible for retrieving any data the page needs, then returning a React element. The server's initial response will include only the rendered HTML plus two script tags - one to load React (typically already in the browser cache) and one to load javascript that responsible for hydrating the current page.

There is one piece of magic required to make SSR and hydration work: Each React element returned by a controller must passed through @ssr/app/clientRoot. At build time, a custom Bun plugin will replace this function call with code that renders the given component to an html string and appends scripts to hydrate the page.

While not necessary, it's often convenient for controllers to use the @server/util/pageController and @server/util/controller/decoratePageProps helpers to add type information and pass common props (e.g. currentUser) to the page component.

const userController = pageController<UserSchema>(async (props) => {
  const user = await client
    .selectFrom('User')
    .selectAll()
    .where('id', '=', props.params.id)
    .executeTakeFirstOrThrow();

  return clientRoot(UserPage, decoratePageProps(props, { user }));
});

export { userController };
function UserPage({ currentUser, user }: PageProps<{ user: User }>) {
  return (
    <Page title={user.name}>
      <div className="flex flex-row items-center gap-2">
        <Avatar url={user.avatarUrl} />
        <span className="font-bold">{user.name}</span>
        {user.id === currentUser?.id && (
          <a href={`/users/${user.id}/edit`}>Edit</a>
        )}
      </div>
    </Page>
  );
}

export { UserPage };

Code Organization

Server code lives in src/server, aliased as @server. Client code lives in src/client, aliased as @client. Shared code (e.g. types, runtime-agnostic utility functions) lives in src/shared, aliased as @shared.

ESlint rules are configured to prevent client code from importing any Bun/Node packages or code outside of the @client and @shared directories. This helps to ensure that server-side code is never referenced in a client bundle.

Server

The server is configured in @server/server.tsx. By default, Nick.js enables ElysiaJS plugins for authentication, Tailwind styles, error handling, compression, rate limiting, securing http response headers, serving static files/folders, and adding request ids. It's recommended to use ElysiaJS schemas to type and validate request inputs (e.g. path params, search params, body). Follow the patterns in @shared/type/controller to define schema validations for ElysiaJS and derive types that can be passed into ControllerProps.

Database

Nick.js uses Kysely to interact with a PlanetScale-hosted MySQL database. Migrations can be found in @server/database/migration. By default, projects come with User, Oauth, and Session tables for basic authentication. New migrations can be run with bun run migrate:to-latest, which will have the effect of auto-generating + saving database types to @shared/type/db/generated.ts. When executing queries, use the client instance from @server/database/client.