How to build a JamStack app with Angular Scully and Prisma
Tags: angular, scully, prisma
Published: 2022-02-02 19:29:35

## TLDR;

> By using Angular  Scully and Prisma we have a powerful ecosystem where we can leverage full type safety and easily connect to a database that contains data that will be injected into the components by Scully. After the Scully process, we can upload our app to a CDN and have a fully working SEO-friendly Angular app.
> 

## What is Prisma?

Prisma is an open-source ORM, it consists of three parts:

- **Prisma Client**: Auto-generated and type-safe query builder for Node.js & TypeScript
- **Prisma Migrate**: Migration system
- **Prisma Studio**: GUI to view and edit data in your database

Prisma Client can be used in *any* Node.js (supported versions) environment.  The power of Prisma is that they have a good and powerful type safety solution when using it with Typescript, and by default supports a set of Easy to use querIes. 

## What is Scully

Scully is the static site generator for Angular projects looking to embrace the [Jamstack](https://jamstack.org/).

It will use your application and will create a static `index.html` for each of your pages/routes. Every `index.html` will have the content already there, and this will make your application show instantly for the user. Also, this will make your application very SEO-friendly. On top of this, your SPA will still function as it did before.

## How to set up an Angular app with Scully?

In this article, we are not going very deep into the setup of Angular and Scully. For a good reference, you can have a look at the [repo](https://github.com/Jefiozie/asp-example). Below are the steps you can follow along:

First, let’s set up a new Angular app

> npx ng new ng-scully --minimal
> 

Second, let’s add Scully to the Angular application

> ng add @scullyio/init
> 

Scully schematics will do the following:

- Add Scully dependencies to `package.json` and install it
- Import `ScullyLibModule` to `AppModule`
- Add `'zone.js/dist/task-tracking'` to `polyfills.ts`
- Add `scully.<project_name>.config.ts` to the root directory. This is Scully configuration file that we will utilize to configure Scully.

Now we have a setup that works with Angular, but we need to take one step more for this demo.

> ng generate @scullyio/init:blog
> 

The above command adds the blog modules' routes to the Angular application.
In addition, it creates a `./blog` folder for the blog's markdown files.

# How to use Prisma with Scully?

I’ve made the choice to use postgress as my database, in combination with docker.

Below I’m showing you my docker-compose file:

```yaml=
version: '3'
services:
 postgres:
    image: postgres
    ports:
      - "5432:5432"
    restart: always
    environment:
      POSTGRES_USER: prisma
      POSTGRES_PASSWORD: prisma
    volumes:
      - postgres:/var/lib/postgresql/data
volumes:
  postgres:
```

Now we only need to run it so that Prisma can connect to it.

> docker-compose up -d
> 

Now we can continue with Prisma, first we need to install Prisma

> npm install prisma --save-dev
> 

After installation we will run the init command as shown below:

> npx prisma init
> 

This init command will setup Prisma with the required files in the directory.

After this we need to change the `.env` file with our database connection:

```graphql
DATABASE_URL="postgresql://prisma:prisma@localhost:5432/mydb?schema=public"
```

### Setup Prisma configuration

To use Prisma with Scully we first need to add the setup for Prisma.

As Prisma is an ORM for a database we need to tell Prisma what tables and/ or database it is connected to. This information is placed in the `schema.prisma` file:

```graphql
// This is your Prisma schema file,

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id        Int      @id @default(autoincrement())
  name      String?
  email     String?  @unique
  createdAt DateTime @default(now()) @map(name: "created_at")
  updatedAt DateTime @updatedAt @map(name: "updated_at")
  posts     Post[]

  @@map(name: "users")
}
```

> Note: You're occasionally using `@map`and`@@map`to to transform some field and model names to different column and table names in the underlying database.
> 

This Prisma schema defines two models, each of which will map to a table in the underlying database: `User` and `Post`. Notice that there's also a relation (one-to-many) between the two models, via the `author` field on `Post` and the `posts` field on `User`.

Now that we have our schema defined, we need to `create` our tables in our database. This can be done by running the following CLI command:

> npx prisma db push
> 

You should see the following output:

```bash
Environment variables loaded from .env
Prisma schema loaded from prisma\schema.prisma
Datasource "db": PostgreSQL database "mydb", schema "public" at "localhost:5432"
The database is already in sync with the Prisma schema.
✔ Generated Prisma Client (3.8.1 | library) to .\node_modules\@prisma\client in
75ms
```

As our database is ready to be used, let’s add some data. We are going to use Prisma Studio, this is an easy way to explore and manipulate the data. You can open up Prisma Studio by running

> npx prisma studio
> 

### Create a Prisma Scully Plugin

As we now have an operating database and an ORM library (Prisma) we now can use all these parts to receive the data and use it in Scully. Let’s start by creating the first basis for our custom plugin.

```tsx
import { PrismaClient } from "@prisma/client";
import {
  HandledRoute,
  logError,
  registerPlugin,
  RouteConfig,
} from "@scullyio/scully";
import { convertAndInjectContent } from "@scullyio/scully/src/lib/renderPlugins/content-render-utils/convertAndInjectContent";
export const prismaPlugin = "prismaPlugin";
const prisma = new PrismaClient();
const routerPlugin = async (route: string, config: RouteConfig) => {
  // here we are looking up all posts
  const data = await prisma.post.findMany({
    // where the published property is true
    where: { published: true },
    // and we include the author
    include: {
      author: {
        // and we only want the author's name
        select: { name: true },
      },
    },
  });
  return Promise.resolve(
    // let's loop over all posts
    data.map((post) => {
      // and return a new route for each post
      const { id, title, published, author, content } = post;
      return {
        ...config,
        route: `/blog/${id}`,
        data: {
          id,
          title,
          published,
          author: author.name,
          content,
        },
      } as HandledRoute;
    })
  );
};
registerPlugin("router", prismaPlugin, routerPlugin);

async function prismaDomPlugin(dom: any, route: HandledRoute | undefined) {
  if (!route) return dom;
  try {
    try {
      // here we use the power of scully and use the filehandler to convert the content to html
      return convertAndInjectContent(dom, route.data.content, "md", route);
    } catch (e) {
      logError(`Error during contentText rendering`);
      console.error(e);
    }
    return dom;
  } catch (e) {}
}

registerPlugin("postProcessByDom", prismaPlugin, prismaDomPlugin);
```

Let’s break this code down from the top.

```tsx
import { PrismaClient } from '@prisma/client';
import { logError, registerPlugin, RouteConfig } from '@scullyio/scully';

//define our plugin name
export const prismaPlugin = 'prismaPlugin';

// setup our PrismaClient
const prisma = new PrismaClient();

// our router plugin
const routerPlugin = async (route: string, config: RouteConfig) => {
...
};
```

Now we are going to retrieve the `posts` with the Prisma client. When all the data is gathered we will return new routes that will be used on our post-render step.

```tsx
const prisma = new PrismaClient();
const routerPlugin = async (route: string, config: RouteConfig) => {
  // here we are looking up all posts
  const data = await prisma.post.findMany({
    // where the published property is true
    where: { published: true },
    // and we include the author
    include: {
      author: {
        // and we only want the author's name
        select: { name: true },
      },
    },
  });
  return Promise.resolve(
    // let's loop over all posts
    data.map((post) => {
      // and return a new route for each post
      const { id, title, published, author, content } = post;
      return {
        ...config,
        route: `/blog/${id}`,
        data: {
          id,
          title,
          published,
          author: author.name,
          content,
        },
      } as HandledRoute;
    })
  );
};
```

The post-process plugin is used to transform the render HTML. In our custom plugin, we make use of the Scully system, the `converAndInjectContent` function will look at the `fileHandler` plugins, and if it finds an extension of a file type.  In our case, it will look for the `fileHandler` for markdown files. This plugin will transform our data coming from the database from markdown to HTML.

```tsx
async function prismaDomPlugin(dom: any, route: HandledRoute | undefined) {
  if (!route) return dom;
  try {
    try {
      // here we use the power of scully and use the filehandler to convert the content to html
      return convertAndInjectContent(dom, route.data.content, "md", route);
    } catch (e) {
      logError(`Error during contentText rendering`);
      console.error(e);
    }
    return dom;
  } catch (e) {}
}
```

Now that we have set up our plugin, we need to make one new change to our Scully configuration. We need to change the original blog route to use our custom plugin, first, we need to import our custom plugin 

```tsx
import { prismaPlugin } from "./scully/plugins/plugin";
```

Then we need to define our router and post process plugin to being used in our blog route.

```tsx
routes: {
    "/blog/:slug": {
      type: prismaPlugin,
    }
  }
```

Finally, we are ready to run our Scully system to scan for new routes, run `npx scully --scan`

```bash
$ npx scully --scan
  ✔ new Angular build files imported
  ✔ Starting servers for project "asp-example"
  ✔ Started Angular distribution server on "http://localhost:1864/" 
  ✔ Started Scully static server on "http://localhost:1668/"
  ✔ Scully Development Server is up and running
  ✔ Puppeteer is being launched
  ✔ Successfully scanned Angular app for routes
  ✔ Successfully added routes created from routePlugins
  ✔ Route list created in files:
     ".\src\assets\scully-routes.json",
     "dist\static\assets\scully-routes.json",
     "dist\asp-example\assets\scully-routes.json"

  ✔ Route "/blog" rendered into ".\dist\static\blog\index.html" 
  ✔ Route "/home" rendered into ".\dist\static\home\index.html" 
  ✔ Route "/" rendered into ".\dist\static\index.html" 
  ✔ Route "/blog/1" rendered into ".\dist\static\blog\1\index.html" 

Total time used 5.74 seconds
  4 pages have been created
  Rendering the pages took 2.99 seconds
  That is 1.34 pages per second,
  or 749 milliseconds for each page.
  
  Finding routes in the angular app took 2.68 seconds
  Pulling in route-data took 47 milliseconds
```

We now have our first page rendered with Angular, Scully, and Prisma.

# Conclusion

With Prisma we have a powerful type of safety solution to connect to a database, combine this with the power of Scully we cn easily create static pages from an Angular application and upload it to a CDN.B