This is a Next.js project bootstrapped with create-next-app
with some additions of common dependencies and configuration to start a new project.
I was a bit tired on having to install the same dependencies or comming up with some "base" for authentication/routing/etc, so this template is an all-in-one repo to quickly bootstrap projects.
Within this template you have:
- chakra as component UI framework
- trpc e2e solution to handle API requests safely + it integrates with react-query to handle requests
- zod schema validation with static type inference [1]
- fp-ts our "utility library" to help us handle some things in a more "functional way"
- tanstack table library to help us handle tables
- react-hook-form to help us handle forms + validation [1]
- prisma our ORM + seeders + authentication + protected routes are already there + zod integration + docker-compose in place for dev database
- next-auth to handle authentication
- envsafe to make sure you don't accidentally deploy apps with missing or invalid environment variables
- next-translate i18n solution
- eslint is already there
- some examples of common things you usually have to do
- form + validation + mutations
- protected routes + routes with simple role validation
- dark/light mode
TODO:
testing environmnet set up + mock server for integration tests
First, run the development server:
pnpm i
cp .env.sample .env # and add the missing variables
docker-compose up -d
pnpm dev
Open http://localhost:3000 with your browser to see the result.
Open up lib/auth.ts
and go nuts with next-auth configuration
Open up lib/theme.ts
and go nuts with chakra configuration
I'm following the defaults of next-translate, so just follow they're guide
While we wayt for Next Layouts rfc...
If you have a common layout, just add to the /layouts
folder and wrap your with your pages
components
I'm using prisma here, no customisation, just change the schema, follow they're documentation and you are good to go.
I like strong typing, and usually I would use a combination with next-connect
+ fp-ts
+ io-ts
+ react-query
, but tRPC
is a cool solution that despite not being so "FP-hardcore", it is very cool on how much you can achieve with that.
I actually don't customize anything from they're guide, so if you read them you should be at home
Everything is within /server
folder and /lib/trpc.ts
file.
If you need new routes, just create a new file within /server/routers/product.ts
import { Prisma } from "@prisma/client";
import { createRouter } from "@/server/create-router";
import { createProtectedRouter } from "@/server/create-protected-router";
import { z } from "zod";
const productRoute = createRouter()
.query("list", {
input: z.object({
name: z.string().nullish(),
}),
resolve: async ({ input, ctx }) => {
const where: Prisma.ProductWhereInput = {
name: {
contains: input?.name ?? undefined,
},
};
const [products, productCount] = await Promise.all([
ctx.prisma.product.findMany({
where,
}),
ctx.prisma.product.count({ where }),
]);
return {
products,
productCount,
};
},
})
// if you want protected routes
.merge(
"admin.",
createProtectedRouter().mutation("create", {
input: z.object({
name: z.string().min(1),
description: z.string().min(1),
}),
resolve: async ({ ctx, input }) => {
const product = await ctx.prisma.product.create({
data: {
name: input.name,
description: input.description,
},
});
return product;
},
})
);
export default productRoute;
Connect it to the main router
// server/routers/_app.ts
import superjson from "superjson";
import { createRouter } from "@/server/create-router";
import healthRoute from "@/server/routers/health";
import productRoute from "@/server/routers/product";
export const appRouter = createRouter()
.transformer(superjson)
.merge("health.", healthRoute)
.merge("product.", productRoute);
export type AppRouter = typeof appRouter;
And then use it:
const Home: NextPageWithAuth = () => {
const [query, setQuery] = useQueryParams({
name: StringParam,
});
const productQuery = trpc.useQuery(["product.list", { ...query }]);
// ... from now on just follow the flow https://trpc.io/docs/nextjs#6-make-api-requests
};
Since I'm using TS, you can directly import files through an alias instead of relative path
@/
maps to <project_root>/
I installed pino as a logger tool which is what is recommended within next.js documentation
Just import /lib/pino.ts
and use it
By opening /lib/types.ts
you'll see NextPageWithAuth
, this is a special type you can give to your pages
components to include automatic authentication validation.
I could rely on next-auth
middlewares, but I don't want to, I would have to enable JWT
option and it seemed straightforward to just solve this in the component layer... that would work for simple projects.
You can check the examples of /pages/only-admin.tsx
or /pages/private.tsx
files, the system is raw and simple:
import NextLink from "next/link";
import { NextPageWithAuth } from "@/lib/types";
const Private: NextPageWithAuth = () => {
return <div>This is a private route</div>;
};
Private.auth = true; // just add this property and πͺ
export default Private;
If you need to validate roles, I have added a very naive and simple role system since most times I just needed regular/admin users, then:
import NextLink from "next/link";
import { NextPageWithAuth } from "@/lib/types";
const Private: NextPageWithAuth = () => {
return <div>This is a private route</div>;
};
Private.auth = {
can: ["ADMIN"],
}; // just add this property and πͺ
export default Private;
TS will help you out there =D
To understand better how that works, check /pages/_app.tsx
, this is just a HoC that checks for that auth
property and render a component that checks for authenticated users π.
Well... /lib/utils.ts
is slim, I just have a function to show a "placeholder image" and to slugify
some text, no real reason on why only those two, usually I end up needing that every time π
/components/FieldErrors.tsx
is a component that... display field errors, just send anreact-hook-form
error variable and it will handle it (please add the remaining validations/translations if you need)/components/FormSection.tsx
I like Laravel Jetstream way on handling forms, so this component just represents a section of a form where you can provide atitle
anddescription
on what that section is/components/Header.tsx
... is the header of the app, light/dark mode toggle button + user auth login/logout link are there
I'm not providing an example with everything, I just need/want eventually handle some operations and I will use fp-ts
for example for some things... I just want to already have some dependencies I use by default.
I know, its a lot of dependencies and config, JS/TS world is insane, but yeah, I could build a project with just Next.js or plain JS, go on with Solid/Svelte/etc or go to Laravel not worry with anything π ... I could build something strict [1] [2] but until now, this template contains everything you'll probably need (and more) for most projects and it's free, spin up Vercel or Railway, provision a database (maybe with Planetscale) and pay nothing, which is amazing.
Still... It wouldn't be as good as Elm [1] π₯²
"sdds Elm", right Sunsi?