Building a blog with Neon serverless database and both NextJS 13 and Sveltekit
This has to be my longest headline ever.
Ever since I learned about Neon I’ve wanted to try it out. A serverless, infinitely scalable Postgres install?!?? How could that not solve all my problems? Today I finally had the time, so let’s do this.
What is Neon, and why should we care?
Neon is serverless, which in this case means that storage and compute (I/O of the db) is separate and scales independently. This means that when you’re not using it, cost goes to almost zero. But also when you need to scale, it can do so automatically. No more splitting databases, or manually spinning up or down database nodes!
Neon also allows us to branch and merge database schemas. So whenever we need to update what your database looks like, you can easily do so, and even test all the way to production (by letting some accounts reach the new branch probably) before rolling out the change.
This means neon is especially suited for apps that rapidly change their datasets. Startups with rapid product iterations should benefit tremendously!
Setup your Neon account
Head over to neon.tech and click sign in. Chose your preferred auth path and you’re done!
Click through the intro-carousel and hit create new project.
Pick a witty web2.0 name, something like Peachforce. Here’s a generator to help you.
Download your .env file to access your database from your web app.
Test the database
Now let’s try out the database. Head over to your Neon project and open up the SQL editor. The Db exists, but it’s totally empty. So we will want to create a few tables and add an extension for generating UUIDs. Neon has a growing list of Postgres extensions you can use, but we’ll have to activate them first.
# activates the uuid extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
# creates the Author table with an ID and Name
create author(
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT
)
# creates the Post table with an ID, Content text, Created timestamp, and foreign key link to an Author
create post(
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
content TEXT,
created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
author uuid REFERENCES author (id)
)
Why text? Why not varchar?
According to this discussion there’s basically no point in using varchar anymore. It was the text type of choice when the length of input mattered. But since postgres 9-10 or so, it really doesn’t. So unless you really know what you’re doing, just use TEXT
.
Add a test author and post to your database to see if it works:
INSERT INTO author(name) VALUES ('your name') RETURNING id // returns the new authors UUID
INSERT INTO post(content, athor) VALUES ( 'Hello World!', '<add the UUID from your author>');
Then head over to the Tables tab
on Neon and click each table in turn to check that your rows are added correctly.
That’s it! Your scalable database is ready to go.
Setup your Nextjs app
Why Nextjs?
If you do any development that touches javascript you probably have heard about Vercel at some point. The company is doing amazing stuff for developer experience and helping us all ship faster with their OSS projects, one of which is the most used React Boilerplate framework: Nextjs. There are already hundreds of tutorials about how to use it though, but they recently released a major change in their structure with Nextjs 13 and I wanted to give it a try.
Create a local folder for our new Nextjs project
mkdir neon-next
Even if you’ve used Nextjs previously, you’ll need to update to the experimental version. So pay attention to this next step:
// Installs or updates to the latest version of NextJS
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
Now create your Nextjs install by running the following command, if you need additional help here with npx etc, please see this guide.
npx create-next-app@latest --typescript
Pick a name for your project or leave it blank to install in the current folder. Not actually sure why it does this, as all other np+
installs are in the current dir. I’m a little surprised both Next
and Svelte
attempt to create subdirectories. (If you know why they do this, please let me know.)
Go through the installation guide, I recommend using ESLINT. But it’s up to you!
Test the install by running npm run dev
Add a .env
file to the root of your project folder with the following string:
// you can copy this URL from the .env file we downloaded from Neon earlier!
DATABASE_URL=postgres://<user>:<password>@<endpoint_hostname>:<port>/<database>
You also have to add ?options=project=my-endpoint-123456
to this url string to support SSL mode in the client connection later. Remember to change “my-endpoint-123456” to your own project ID. Which you can find on the settings page of your neon project!
Add the following to your next.config.js
to activate their experimental /app
folder, which is what I want to test!
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
appDir: true,
},
}
module.exports = nextConfig
Add a /app
directory in your project root, so we can use the new easier-to-use data fetching in Nextjs 13.
Add a layout.tsx
to the /app
directory, this file is required and used to render the html and body blocks of your site. If you’ve used Nextjs before this file practically replaces the _app.js and _document.js files in earlier Nextjs projects.
There are a bunch of special purpose files in Nextjs 13 that speed up development significantly. Learn about them in detail here.
Here is my layout.tsx
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
After adding your layout.tsx
you can remove the /pages
directory which we won’t use.
Now add a page.ts
to the /app
directory, with the following code:
import postgres from "postgres";
import { Suspense } from "react";
import Link from "next/link";
async function getData(query: any) {
//@ts-ignore
const sql = postgres(process.env.DATABASE_URL, {
ssl: require,
});
const result = await sql.unsafe(query);
// The return value is *not* serialized
console.log("backend result", result);
// You can return Date, Map, Set, etc.
return result;
}
// This is an async Server Component
export default async function StartPage() {
const data = await getData("select * from post");
return (
<div>
<h2>Testing SQL</h2>
<Suspense fallback={<div>Loading...</div>}>
<ul>
{data.map((item: any) => (
<li key={item.id}>{item.content}</li>
))}
</ul>
</Suspense>
</div>
);
}
This might seem strange. But Nextjs leverages the power of serverless edge functions to run “server side” code right in your component. Magic for developer overview!
And that’s it! Run npm run dev
to test the database connection locally and print the list of posts. If your database has hibernated (fallen asleep because no one was using it) the first call might take 2s to complete. I had no timeout issues with this, and if your app is being used regularly, I don’t think you’ll ever notice.
Setup your Sveltekit app
Nextjs
is amazing. But if you don’t believe [React](https://reactjs.org)
will be the future of the internet (it’s still developed by Facebook, so who knows), perhaps you’re more interested in [Svelte](https://kit.svelte.dev)
?
Svelte is an amazing reactive framework created to show that staying close to web standards and not reinventing the wheel with custom javascript rendering can be, not just a really nice developer experience, but also faster than react!
Sveltekit is the latest release by the Svelte group to create a framework similar to Nextjs, to speed up development. I’ve wanted to try it for a while, so I’m excited to to do this test!
Create a new local directory for our Sveltekit project, if you need more instructions follow this guide.
mkdir neon-svelte
Install sveltekit
// the "." means current directory, you can also add a name to create a subdirectory
npm create svelte@latest .
Svelte offers a lot more guidance on what you want to install with your project than Nextjs does. Choose according to your preference, but I went all defaults.
Choose the skeleton project
template and then pick whichever options you prefer. I used Typescript and then all defaults.
Now we install the dependencies we need
// installs all the general dependencies svelte and svelte kit needs
npm install
// also install the Postgres library that we need to communicate with Neon
npm install --save postgres
Test the install by running npm run dev
Add a .env file to your project folder with the following string, just like we did for Nextjs
// you can copy this URL from the .env file we downloaded from Neon earlier!
DATABASE_URL=postgres://<user>:<password>@<endpoint_hostname>:<port>/<database>
You also have to add ?options=project=my-endpoint-123456
to this url string to support SSL mode in the client connection later. Remember to change “my-endpoint-123456” to your own project ID. Which you can find on the settings page of your neon project!
Now replace the content of /routes/+page.svelte
with
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<h2>Testing postgres</h2>
<ul>
{#each data.post as post}
<li>
{post.content}
</li>
{/each}
</ul>
Add the file /routes/+page.server.ts
to automatically handle serverside calls for this route. What this means is that the code in this file will be run “server side” each time this route is called.
//@ts-ignore - some type error happens in the import that I didn't have time to look into
import * as db from '$lib/server/database.js';
/** @type {import('./$types').PageServerLoad} */
export async function load() {
return {
post: await db.getPosts()
};
}
And finally add the general purpose db connector to a completely serverside function to keep environment variables secure etc in /lib/server/database.js
import { DATABASE_URL } from '$env/static/private';
import postgres from 'postgres';
const sql = postgres(DATABASE_URL, {
ssl: 'require'
});
export async function getPosts() {
const posts = await sql`SELECT * FROM post`;
return posts;
}
Everything file in the /lib/server
directory creates serverless functions on your host that can be used for internal or even public APIs.
That’s it! Now we have the same app up and running with sveltekit. Compared to Nextjs it’s a little more verbose with the extra server-files. But it also makes it that much harder to accidentally publish server side code to the client. Something that is already difficult with Nextjs and more so in Nextjs 13’s magic /app
directory.
Publish your app
Nextjs is amazing. But I think Vercel’s hosting service might be even more amazing. If you don’t already have a Vercel account set one up.
After which you can either install their CLI tool or connect your GIT repository to easily publish our project. I recommend the CLI tool for development.
After installing, just run this command and follow the guide.
vercel
In a few seconds your project is live and the URL is shown in the terminal window. Clicking it should show you the list of posts in your completely serverless new blog!
My thoughts
This is a really quick guide, so please let me know if I’ve missed steps or you’re wondering about anything.
Neon
Neon seems amazing. It’s way too powerful for this measly test though. I have a hunch what I would use it for but I am blown away by how much time and money we would have saved at some of my previous startups.
Nextjs 13
Nextjs was already amazing. The new structure of Nextjs is even more amazing. But you really have to double check you’re not publishing any secrets to the browser.
Sveltekit
Svelte has been my preferred language of choice ever since I discovered it. I’ve long thought it would gather momentum and overtake React, at least for developer side projects, just because it’s easier to use.
Sveltekit really solves the last issues with using Svelte by giving you all the infrastructure for a project right out of the box.
That said, I think I prefer the flavour of the code and file structure of Nextjs… Maybe it’s just that I’m more used to React? I will have to try some other experiment before I feel comfortable saying what I would choose.