├── .gitignore ├── drizzle └── 20230126114212 │ ├── migration.sql │ └── snapshot.json ├── .env ├── src ├── schema.ts ├── client.ts └── server.ts ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /drizzle/20230126114212/migration.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "users" ( 2 | "id" serial PRIMARY KEY NOT NULL, 3 | "name" text NOT NULL, 4 | "email" text, 5 | "role" text 6 | ); 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ## see https://neon.tech/docs/guides/node/ 2 | PGHOST=':' 3 | PGDATABASE='' 4 | PGUSER='' 5 | PGPASSWORD='' 6 | ENDPOINT_ID='' -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import { pgTable, serial, text } from "drizzle-orm/pg-core"; 2 | import { z } from "zod"; 3 | import { createInsertSchema } from "drizzle-zod/pg"; 4 | 5 | export const users = pgTable("users", { 6 | id: serial("id").primaryKey(), 7 | name: text("name").notNull(), 8 | email: text("email"), 9 | projectRole: text<"admin" | "user">("role"), 10 | }); 11 | 12 | export const apiUser = createInsertSchema(users, { 13 | projectRole: z.enum(["admin", "user"]), 14 | }); 15 | export const apiCreateUser = apiUser.omit({ id: true }) 16 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | // @filename: client.ts 2 | import { createTRPCProxyClient, httpBatchLink } from "@trpc/client"; 3 | import type { AppRouter } from "./server"; 4 | 5 | // Notice the generic here. 6 | const trpc = createTRPCProxyClient({ 7 | links: [ 8 | httpBatchLink({ 9 | url: "http://127.0.0.1:4000/trpc", 10 | }), 11 | ], 12 | }); 13 | 14 | const main = async () => { 15 | const [inserted] = await trpc.createUser.mutate({ 16 | name: "Daniel", 17 | email: "daniel@radcliffe.com", 18 | projectRole: "user", 19 | }); 20 | 21 | const user = await trpc.userById.query(inserted.id); 22 | console.log(user); 23 | 24 | const all = await trpc.users.query(); 25 | console.log(all) 26 | }; 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drizzle-trpc-zod", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start:server": "tsx src/server.ts", 8 | "start:client": "tsx src/client.ts", 9 | "generate": "drizzle-kit generate:pg --schema=src/schema.ts" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@trpc/client": "^10.9.0", 16 | "@trpc/server": "^10.9.0", 17 | "dotenv": "^16.0.3", 18 | "drizzle-orm": "^0.17.0", 19 | "drizzle-zod": "0.1.3", 20 | "express": "^4.18.2", 21 | "postgres": "^3.3.3", 22 | "zod": "^3.20.2" 23 | }, 24 | "devDependencies": { 25 | "@types/express": "^4.17.16", 26 | "drizzle-kit": "^0.16.6", 27 | "tsx": "^3.12.2" 28 | } 29 | } -------------------------------------------------------------------------------- /drizzle/20230126114212/snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "dialect": "pg", 4 | "id": "c46ed056-8cc6-4585-b571-6e2c1c2eb860", 5 | "prevId": "00000000-0000-0000-0000-000000000000", 6 | "tables": { 7 | "users": { 8 | "name": "users", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "name": { 18 | "name": "name", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true 22 | }, 23 | "email": { 24 | "name": "email", 25 | "type": "text", 26 | "primaryKey": false, 27 | "notNull": false 28 | }, 29 | "role": { 30 | "name": "role", 31 | "type": "text", 32 | "primaryKey": false, 33 | "notNull": false 34 | } 35 | }, 36 | "indexes": {}, 37 | "foreignKeys": {} 38 | } 39 | }, 40 | "enums": {}, 41 | "schemas": {} 42 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Drizzle ORM + Neon + tRPC + Zod 2 | This is an example repo to showcase [Neon database](https://neon.tech) + [tRPC](https://trpc.io) + [zod](https://zod.dev) and [drizzle-orm](http://driz.li/orm) native integration. 3 | 4 | We've implemented a native Zod module for Drizzle ORM so you can rapidly implement APIs with Zod validations 5 | 6 | ```typescript 7 | import { pgTable, serial, text } from "drizzle-orm-pg"; 8 | import { createInsertSchema } from "drizzle-zod/pg"; 9 | import { z } from "zod"; 10 | 11 | export const users = pgTable("users", { 12 | id: serial("id").primaryKey(), 13 | name: text("name").notNull(), 14 | email: text("email"), 15 | projectRole: text<"admin" | "user">("role"), 16 | }); 17 | 18 | export const apiUser = createInsertSchema(users, { 19 | projectRole: z.enum(["admin", "user"]), 20 | }); 21 | 22 | // zod schema for API user creation 23 | export const apiCreateUser = apiUser.omit({ id: true }) 24 | ``` 25 | 26 | To run example let's install `node_modules` 27 | ```shell 28 | npm i 29 | ``` 30 | 31 | Prepare you Neon database and get all the needed credentials and put them to `.env` 32 | ``` 33 | ## see https://neon.tech/docs/guides/node/ 34 | PGHOST=':' 35 | PGDATABASE='' 36 | PGUSER='' 37 | PGPASSWORD='' 38 | ENDPOINT_ID='' 39 | ``` 40 | 41 | then just 42 | ```shell 43 | npm run start:server 44 | npm run start:client 45 | ``` 46 | 47 | You can also alter `src/schema.ts` and generate new SQL migrations automatically with drizzle-kit just by running. Give it a try, it's very useful. 48 | ```shell 49 | npm run generate 50 | ``` 51 | 52 | Help us grow! 53 | - follow us on [Twitter](https://twitter.com/DrizzleOrm) 54 | - give us a star on [GitHub](https://github.com/drizzle-team/drizzle-orm) 55 | - join our community on [Discord](https://driz.li/discord) -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC, inferAsyncReturnType } from "@trpc/server"; 2 | import { z } from "zod"; 3 | import { drizzle } from "drizzle-orm/postgres.js"; 4 | import { migrate } from "drizzle-orm/postgres.js/migrator"; 5 | import postgres from "postgres"; 6 | import { users, apiUser, apiCreateUser } from "./schema"; 7 | import { eq } from "drizzle-orm/expressions"; 8 | import * as trpcExpress from "@trpc/server/adapters/express"; 9 | import express from "express"; 10 | import { config } from "dotenv"; 11 | 12 | // see https://neon.tech/docs/guides/node/ 13 | config(); 14 | const { PGHOST, PGDATABASE, PGUSER, PGPASSWORD, ENDPOINT_ID } = process.env; 15 | const URL = `postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}?options=project%3D${ENDPOINT_ID}`; 16 | 17 | const connection = postgres(URL, { ssl: "require", max: 1 }); 18 | const db = drizzle(connection); 19 | 20 | const createContext = ({}: trpcExpress.CreateExpressContextOptions) => ({}); // no context 21 | type Context = inferAsyncReturnType; 22 | 23 | export const t = initTRPC.context().create(); 24 | 25 | export const appRouter = t.router({ 26 | users: t.procedure.query(async () => { 27 | return await db.select(users); 28 | }), 29 | userById: t.procedure.input(z.number()).query(async (req) => { 30 | const result = await db.select(users).where(eq(users.id, req.input)); 31 | return result[0]; 32 | }), 33 | createUser: t.procedure.input(apiCreateUser).mutation(async (req) => { 34 | return await db.insert(users).values(req.input).returning(); 35 | }), 36 | }); 37 | 38 | export type AppRouter = typeof appRouter; 39 | 40 | const app = express(); 41 | app.use( 42 | "/trpc", 43 | trpcExpress.createExpressMiddleware({ 44 | router: appRouter, 45 | createContext, 46 | }) 47 | ); 48 | 49 | const main = async () => { 50 | await migrate(db, { migrationsFolder: "./drizzle" }); 51 | 52 | app.listen(4000, () => { 53 | console.log("listening on http://127.0.0.1:4000"); 54 | }); 55 | }; 56 | 57 | main(); 58 | --------------------------------------------------------------------------------