GitHub

Demo API

This is the recommended starting point if you'd like to see Noun & Verb in action, but don't have an existing Prisma schema.

Prerequisites

  • A current version of node.js. Noun & Verb is tested against versions 14, 16 & 18.
  • It helps to use a modern IDE like VS Code. It's what we use.
  • We presume an understanding of Prisma, it's purpose and the problems it solves.

Step 1: Initialize a demo API module

Open a terminal into a directory that you can create the demo API within.

The easiest way to get started with Noun & Verb is to use it's creator module

npm init noun-and-verb

Since we are trying the demo, just press ENTER when presented with the dialog below.

npm init noun-and-verb
? Would you like to see a demo? (Use arrow keys)
❯ demo
  new api

You are then presented with an opportunity to name your API. Just pressing enter will name it noun-and-verb-demo. However you can call it whatever you'd like. This is also used as the npm module name for the API server.

It's a good idea to keep it simple with file & URI safe strings (a-zA-Z_-)

npm init noun-and-verb
? Would you like to see a demo? demo
? What would you like to name your API? (noun-and-verb-demo)

The last question before we are ready to create the starting point, is the directory name for the API. By default, it'll use the same name as the API name. But you can all it something different.

npm init noun-and-verb
? Would you like to see a demo? demo
? What would you like to name your API? noun-and-verb-demo
? Which directory would you like to use? noun-and-verb-demo

The create-noun-and-verb now goes to work for you:

init noun-and-verb (0.0.25) (demo=noun-and-verb-demo)
ℹ Initializing git repository
ℹ Initializing package.json
ℹ Setting up .env file
ℹ Initializing demo models in prisma/schema.prisma
ℹ Installed dependencies (38.7s)
✔ Successfully initialized 'noun-and-verb-demo', a demo noun-and-verb API

This has created a starting point for the demo repo, using a SQLite as the database. This allows you to get started with the minimal fuss.

At this point we are ready to move forward. The command also helpfully prints the next steps. These might seem a bit overwhelming, but worry not! We'll break it down, step-by-step and you'll be a master of this universe in no time at all.

Next steps:
  cd noun-and-verb-demo
  # TODO: inspect example model in prisma/schema.prisma
  npm run generate
  export NODE_ENV=dev # protect production DB
  npm run migrate # synchronize schema with 'sqlite'
  npm run test
  npm run dev # starts the dev server


   visit http://localhost:1234 to exercise your API!


For more, visit https://NounAndVerb.io

Let's change directory to our API server.

> cd noun-and-verb-demo

Step 2: Inspect Prisma Schema

Now we are ready for a first inspection of the demo Prisma schema.

If you haven't seen a prisma schema before, this might look overwhelming. Prisma Schema documentation is the authoritative source. In short, there are three parts of the Prisma Schema -

  • datasource: Defines which database flavor we'd like to use (one per schema)
  • generators: Operation on the schema to generate all necessary code. Noun & Verb is implemented as a generator.
  • models: These are the database tables and fields that represent your application needs.

To view the demo schema.prisma:

code -n prisma/schema.prisma

Datasource

The Demo API uses a SQLite database, to minimize the fuss involved.

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

Generators

The demo API is setup with two generators:

  1. prisma-client-js : Responsible for generating the default Prisma client.
  2. noun-and-ver: Responsible for generating a GraphQL API as advertised.
generator client {
  provider = "prisma-client-js"
}


generator noun_and_verb {
  // This is the noun & verb generator
  // Learn more about it in the docs: https://NounAndVerb.io/docs
  provider = "noun_and_verb"
}

Models

models are the raison dêtre of Prisma. They define the database tables, and code is generated around it.

A vanilla Prima model

This is what Prisma requires to generate a client.

model User {
  id        String   @id @default(cuid())
  name      String
  username  String
  email     String
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  cart      Cart?
}

Noun & Verb Annotations

Noun & Verb adds a few annotations to work it's magic. We'll introduce these step-by-step to keep things sane.

@readOnly Annotation

This is a field level annotation, added to fields that the modifiable on the server, but should never be written to by a client. These fields are missing from any create/update operations.

Typical examples are Ids, createdAt/updatedAt timestamps etc.

model User {
  /// @readOnly
  id        String   @id @default(cuid())
  name      String
  username  String
  email     String
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}

@writeOnly Annotation

This a field level annotation, added to fields that are writable by the client, but not accessible after. These fields are missing from any read operations.

Typical example of this is a password field.

model User {
  /// @readOnly
  id        String   @id @default(cuid())
  name      String
  username  String
  email     String
  /// This is a password
  /// @writeOnly
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}

@createOnly Annotation

A field level annotation, specifies fields that are writable only on creation, but readOnly after. These fields are missing from any update operations.

Typical example of this is a username field.

model User {
  /// @readOnly
  id        String   @id @default(cuid())
  name      String
  /// @createOnly
  username  String
  email     String
  /// This is a password
  /// @writeOnly
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}

@scalar Annotation

A field level annotation, provides validation of field values, and serialization/deserialization of the value across the wire.

76 different scalars are supported out-of-the-box. It's possible to define your own scalars easily. Please see scalar documentation for details.

model User {
  /// @readOnly
  id        String   @id @default(cuid())
  name      String
  /// @createOnly
  username  String
  /// @scalar Email
  email     String
  /// This is a password
  /// @writeOnly
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}

@seed Annotation

A model level annotation, specifies which models to use as "roots" for seed (synthetic) data generation. Necessary since Relational graphs within a Database are cyclical by nature. The @seed annotation helps specify the "roots" of directed-acyclic-graphs.

Note, that you can have as many seeds as you'd like. For large schemas, it's wise to be selective, since the seed annotation is used to create 100 elements of the root node and depending on your Database connection specifics, this can add up in time.

Please see seed annotation for details.

/// @seed
model User {
  /// @readOnly
  id        String   @id @default(cuid())
  name      String
  /// @createOnly
  username  String
  /// @scalar Email
  email     String
  /// This is a password
  /// @writeOnly
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}

@mock Annotation

A field level annotation, controls the synthetic data generated for a field.

Uses faker.js to provide built in support for these 155 types. It's easy to add custom generators. Please see @mock documentation for more details.

/// @seed
model User {
  /// @readOnly
  id        String   @id @default(cuid())
  /// @mock faker.name.firstName
  name      String
  /// @createOnly
  username  String
  /// @scalar Email
  email     String
  /// This is a password
  /// @writeOnly
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}

The complete annotated models

We've just added the minimal set of annotations on the other modes.

/// @seed
model User {
  /// @readOnly
  id        String   @id @default(cuid())
  /// @mock faker.name.firstName
  name      String
  /// @createOnly
  username  String
  /// @scalar Email
  email     String
  /// This is a password
  /// @writeOnly
  password  String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  cart      Cart?
}


model Cart {
  /// @readOnly
  id        String    @id @default(cuid())
  /// @readOnly
  createdAt DateTime  @default(now())
  /// @readOnly
  updatedAt DateTime  @updatedAt
  user      User      @relation(fields: [userId], references: [id])
  userId    String    @unique
  items     Product[]
  coupon    String?
}


model Product {
  /// @readOnly
  id        String   @id @default(cuid())
  name      String
  price     Int
  image     String
  /// @readOnly
  createdAt DateTime @default(now())
  /// @readOnly
  updatedAt DateTime @updatedAt
  carts     Cart[]
}

The workflow

So far, we have only learnt about how to specify our intent via the schema. Now, it's time to use it to deliver value.

When we created the npm module for our API in step 1, we installed all necessary dependencies and setup npm run-scrips. Which are just wrappers around commands we'd need to invoke, with sensible defaults. These are exposed in their raw form, so you have the chance to configure them should the need arise in your situation.

Step 3: Invoke the generators

This command invokes the Prisma cli, and invokes both the generators we learnt about in Step #2.

> npm run generate
> noun-and-verb-demo@0.0.1 generate
> prisma generate && npm run todo


Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
  Noun & Verb:
    1. generated GraphQL specs (CRUD)
    2. generated Prisma DB seeders
    3. generated GraphQL resolvers (CRUD)
    4. generated tests


✔ Generated Prisma Client (3.15.2 | library) to ./node_modules/@prisma/client in 44ms


✔ Generated Noun & Verb - GraphQL generator for Prisma to ./ in 1.51s
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client


import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()


> noun-and-verb-demo@0.0.1 todo
> npx leasot --ignore '**/*.sdl,**/*.json,**/*.snap' src


 ✔ No todos/fixmes found