How I built my portfolio using Next.JS and SQLite DB — [Part 1]

Krishna Prasad
5 min readApr 13, 2024

Starting with Next.JS, Router management, State management

Profile with all requirements

Hey there fellow devs! Some time ago while chilling with my fellow developer, we started talking about JavaScript and he pulled up Next.JS on how it has advanced in the last few years since our first project together back in 2019. It piqued my interest ever since and I decided to revamp my portfolio using Next.JS.

And so, since I revamped my portfolio, why not share my journey with you all? It is a pretty lengthy project, so I will be breaking this down in parts focussing on one setup at a time so that it makes it easy for understanding. In this part, I will mainly be focussing on —

  1. Creating a new Next.JS project
  2. Project structure (the defaults that come with Next.JS standard installation)
  3. Routing and how it works
  4. State management using zustand

1. Create Next.JS project

Creating a new Next.JS project is pretty straight forward. Run the below command in your preferred folder and answer a few questions —

npx create-next-app@latest

The output looks something like this —

O/P of create-next-app command

I am pretty sure most of the questions are self explanatory. Like OFCOURSE TypeScript, ESLint, Tailwind CSS. Code always looks better when grouped, so why not /src directory? But then we have this AppRouter, what’s up with that? I remember having used PageRouter with Next.JS back then. So let us just have a quick comparision between the new AppRouter and PageRouter to decide which one to use in our app —

  1. AppRouter is a server-centric routing system where PageRouter is client-centric — this means it operates on the server side and handles routing for server-rendered pages and API endpoints.
  2. Above point in turn means the performance using AppRouter is better than PageRouter, but at the same time, I feel the optimisation would not reflect the same in large applications.
  3. AppRouter is more flexible compared to PageRouter considering for PageRouter, you must take precautions to ensure page structure exactly as you need the routes to look like.
  4. AppRouter, since it is server-centric, it supports server components much easier compared to PageRouter.

2. Using the AppRouter

With that done, let us look at how AppRouter works in general — we will need to add a page.tsx in the folder that you want to be considered as a route. If you are the kind of dev who generally follows folder structures like /app/admin/login/index.tsx, all you gotta do is replace index.tsx with page.tsx and have your page logic in page.tsx 😉 but if you are the kind that does /app/admin/login.tsx you’ll have to get used to the above structuring!

Let us look at setting up a very basic /admin/login route using AppRouter. That will probably give you a better idea of how things work.

Just a basic /admin/login Next.JS AppRouter

Apologies for the code in an image format 😆. My intention is to

And boom! That’s all. Very simply put, in the above screenshot, when you enter the URL localhost:3000/admin, the redirect method will redirect you to the /admin/login route. Which inturn (the page.tsx inside the login folder) is rendered which holds and exports the React.FC Login().

3. State management using zustand

Now that we have a basic understanding of AppRouter, let us look at one last and very important bit — state management. For state management, I will be choosing zustand.

Why not redux? you may ask. The answer is simple — Redux is comprehensive but complex. Zustand is easy to setup, easy to use and sufficient for the usecase.

Let us first add the zustand package —

yarn add zustand

We need to create a corresponding store — let us split this in 2 parts for our use. One is where we maintain a simple state of a boolean isAuthenticated and a more persistent requirement for a token value in the cookieStorage on login.

// src/app/store/index.ts
export interface AppStoreState {
isAuthenticated: boolean;
setIsAuthenticated: (authStatus: boolean) => void;
}

export const useAppStore = create<AppStoreState>(set => ({
isAuthenticated: cookieStorage.getItem("token") !== null,
setIsAuthenticated: (isAuthenticated: boolean) => set({isAuthenticated}),
}));

That’s simply it for the AppStore! now all you have to do is import the set function and call it on login 🤷‍♂

// login.tsx
const { setIsAuthenticated } = useAppStore();

const login = (formData: LoginFormStruct) => {
apiRef.login(formData).then(res => {
setIsAuthenticated(true);
}
}

And now you always have access to the isAuthenticated variable anywhere in the app.

Here’s how you persist the token value — remember you don’t need to actually have one store and pass it to a Provider like redux. Here you have the flexibility to spearate out your stores for easier readability and access!

// src/app/store/index.ts
export interface TokenStoreState {
token: string | null;
setToken: (token: string) => void;
}

export const useTokenStore = create(
persist<TokenStoreState>(
(set) => ({
token: cookieStorage.getItem('token') as unknown as string || null,
setToken: (token: string) => set({ token }),
}),
{
name: 'token',
storage: cookieStorage,
}
)
);

On your login function, you can call the setToken method with your accessToken string value and it gets stored under your cookieStorage with the name token. If you are using cookieStorage like I am, you will probably need to write a getter setter for cookie parsing. That is exactly the cookieStorage in the above snip. Add this to your store/index.ts

// src/app/store/index.ts
const cookieStorage: PersistStorage<TokenStoreState> = {
getItem: (name: string) => {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) {
const cookieValue = decodeURIComponent(match[2]);
const parsedCookie = JSON.parse(cookieValue);
return parsedCookie.state.token;
}
return null;
},
setItem: (name: string, value: StorageValue<TokenStoreState>) => {
document.cookie = `${name}=${encodeURIComponent(JSON.stringify(value))}; SameSite=Strict; path=/`; // Encode and stringify value before setting cookie
},
removeItem: (name: string) => {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict; path=/`;
},
};

Remember you can customise your getItem function as you desire and make it more generic. My aim here is more focussed toward managing only the token in the cookie storage, so I am very specifically returning only the token string from the store.

Congratulations!!! 🥳 🎉 👏 With this, you have setup your Next.JS app with Tailwind CSS (auto installed on create-next-app), Next.JS AppRouter and Zustand for state management.

In the Part 2 of this journey, we will be walking through creating a database using SQLite DB, how you would connect to it, use it to create some basic routes, tables and data management in Next.JS.

Until then! May the code be with you ✋ peace out and CHEERS!

Next steps — Part 2.

--

--

Krishna Prasad

ReactJS, NextJS, NodeJS and every other JS! It’s like a never ending learning journey. 😎